<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed_style.xsl" type="text/xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="https://www.rssboard.org/media-rss">
  <channel>
    <title>Matteo Lisotto - Personal blog</title>
    <link>https://lisot.to/</link>
    <description>Recent content on Matteo Lisotto - Personal blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>Matteo Lisotto - [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/).</copyright>
    <lastBuildDate>Fri, 30 Aug 2024 08:51:00 +0200</lastBuildDate><atom:link href="https://lisot.to/index.xml" rel="self" type="application/rss+xml" /><icon>https://lisot.to/img/icon.svg</icon>
    
    
    <item>
      <title>Building an Arbitrage Bot on Starknet: Part 3 - Conclusions</title>
      <link>https://lisot.to/posts/starknet-arbitrage-conclusions/</link>
      <pubDate>Fri, 30 Aug 2024 08:51:00 +0200</pubDate>
      
      <guid>https://lisot.to/posts/starknet-arbitrage-conclusions/</guid>
      <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>This is the last article in the series where I describe the small improvements I
made and publish the link to the github repository.</p>
<p>If you missed the previous article and you want to read them:</p>
<ul>
<li><a href="https://lisot.to/posts/starknet-arbitrage-fundamentals/">Building an Arbitrage Bot on Starknet: Part 0 - Understanding the
Fundamentals</a></li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-basics/">Building an Arbitrage Bot on Starknet: Part 1 - The basics</a></li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-bot/">Building an Arbitrage Bot on Starknet: Part 2 - The bot</a></li>
<li>Building an Arbitrage Bot on Starknet: Part 3 - Conclusions</li>
</ul>
<h1 id="improvements-made">Improvements made</h1>
<p>I ended my previous article by describing what improvements could be made. In
this short time (there were holidays) I worked on</p>
<ul>
<li>Made a more robust loop. Now if a coroutine fails, the bot crashes.</li>
<li>Removed <code>ccxt</code>. Implemented the <code>Binance</code> class using the Binance APIs.</li>
<li>Fixed some bugs and refactored a bit <code>AVNU</code></li>
</ul>
<p>In the future, I am still interested in removing AVNU and creating a more robust
infrastructure.</p>
<h1 id="starkshift">StarkShift</h1>
<p>You can find the repository of StarkShift
<a href="https://github.com/Oghma/StarkShift">here</a>. Development will continue on github
and I will only write articles when there are interesting developments or
features.</p>
<p>I just wanted to describe how an arbitrage bot works and show how easy it is to
create a new one.</p>
]]></description>
      
    </item>
    
    
    
    <item>
      <title>Building an Arbitrage Bot on Starknet: Part 2 - The bot</title>
      <link>https://lisot.to/posts/starknet-arbitrage-bot/</link>
      <pubDate>Mon, 19 Aug 2024 18:00:00 +0200</pubDate>
      
      <guid>https://lisot.to/posts/starknet-arbitrage-bot/</guid>
      <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>This article is part of a series on building an arbitrage bot between
centralised (CEX) and decentralised (DEX) exchanges on Starknet.</p>
<p>In this article I will finally cover the bot implementation, explaining the
interfaces, the communication with the exchanges and the <code>Arbitrage</code> class.</p>
<ul>
<li><a href="https://lisot.to/posts/starknet-arbitrage-fundamentals/">Building an Arbitrage Bot on Starknet: Part 0 - Understanding the
Fundamentals</a></li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-basics/">Building an Arbitrage Bot on Starknet: Part 1 - The basics</a></li>
<li>Building an Arbitrage Bot on Starknet: Part 2 - The bot</li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-conclusions/">Building an Arbitrage Bot on Starknet: Part 3 - Conclusions</a></li>
</ul>
<h1 id="project-overview">Project overview</h1>
<p>The bot is written in Python 3.12 using <code>asyncio</code>. It makes extensive use of
coroutines and some of the features offered by <code>Asyncio</code>. If you not know how
<code>asyncio</code> works, you can start
<a href="https://docs.python.org/3/library/asyncio.html">here</a>.</p>
<p>As I mentioned in my previous article, I decided to use
<a href="https://python-poetry.org/">poetry</a> as a dependency manager. One of its
features is automatically creating the project folder when <code>poetry new</code> is
executed. The folder looks like this:</p>
<pre tabindex="0"><code>.
├── Dockerfile
├── README.md
├── poetry.lock
├── pyproject.toml
├── starknet_arbitrage
│   ├── __main__.py
│   ├── arbitrage.py
│   ├── core
│   │   └── types.py
│   ├── exchange
│   │   ├── base.py
│   │   ├── cex
│   │   │   └── binance.py
│   │   └── dex
│   │       └── avnu.py
│   └── starknet.py
└── tests
    └── __init__.py
</code></pre><ul>
<li><code>__main__.py</code> loads the setting and starts the bot</li>
<li><code>arbitrage.py</code> contains the logic of the bot</li>
<li><code>core</code> defines common types</li>
<li><code>exchange</code> handles the communication between centralised, decentralised
exchanges and the bot</li>
<li><code>starknet.py</code> handles the communication between Starknet and the bot</li>
</ul>
<h1 id="core">Core</h1>
<p>The module defines types and logic useful for the whole library. Currently, I
have only defined common types in <code>core/types.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Token</span>:
</span></span><span style="display:flex;"><span>    name: str
</span></span><span style="display:flex;"><span>    address: str <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    decimals: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">18</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __str__(self) <span style="color:#f92672">-&gt;</span> str:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>type(self)<span style="color:#f92672">.</span>__name__<span style="color:#e6db74">}</span><span style="color:#e6db74">(name=</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">)&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Symbol</span>:
</span></span><span style="display:flex;"><span>    base: Token
</span></span><span style="display:flex;"><span>    quote: Token
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Ticker</span>:
</span></span><span style="display:flex;"><span>    raw: dict
</span></span><span style="display:flex;"><span>    bid: Decimal
</span></span><span style="display:flex;"><span>    bid_amount: Decimal
</span></span><span style="display:flex;"><span>    ask: Decimal
</span></span><span style="display:flex;"><span>    ask_amount: Decimal
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Wallet</span>:
</span></span><span style="display:flex;"><span>    raw: dict
</span></span><span style="display:flex;"><span>    token: Token
</span></span><span style="display:flex;"><span>    amount: Decimal
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __str__(self) <span style="color:#f92672">-&gt;</span> str:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>type(self)<span style="color:#f92672">.</span>__name__<span style="color:#e6db74">}</span><span style="color:#e6db74">(token=</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>token<span style="color:#e6db74">}</span><span style="color:#e6db74">, amount=</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>amount<span style="color:#e6db74">}</span><span style="color:#e6db74">)&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">empty</span>(token: Token) <span style="color:#f92672">-&gt;</span> Wallet:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> Wallet({}, token, Decimal(<span style="color:#ae81ff">0</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Order</span>:
</span></span><span style="display:flex;"><span>    raw: dict
</span></span><span style="display:flex;"><span>    symbol: Symbol
</span></span><span style="display:flex;"><span>    amount: Decimal
</span></span><span style="display:flex;"><span>    price: Decimal
</span></span><span style="display:flex;"><span>    side: str
</span></span></code></pre></div><p>The names of the classes are rather self-explanatory. <code>raw</code> fields contain the
original message sent by the exchange. They can be used for debugging or
extracting exchange-dependent information, as required by AVNU.</p>
<p><a href="https://docs.python.org/3/library/decimal.html"><code>Decimal</code></a> objects provide
support for fast, correctly-rounded decimal floating-point arithmetic. Although
they are slower than <code>float</code>, they are a good compromise for avoiding the
floating- point errors that <code>float</code> suffers from.</p>
<h1 id="exchange-comunication">Exchange comunication</h1>
<p>Although centralised and decentralised exchanges are completely different
services, with different features and different APIs, I want a unified interface
so the bot can work easily. We are only interested in:</p>
<ul>
<li>subscribe to a ticker</li>
<li>send market orders</li>
</ul>
<p>Based on this, the <code>Exchange</code> interface is defined as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Exchange</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@abc.abstractmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">subscribe_ticker</span>(self, symbol: Symbol, <span style="color:#f92672">**</span>_):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Subscribe to the ticker channel.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@abc.abstractmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">buy_market_order</span>(self, symbol: Symbol, amount: Decimal, <span style="color:#f92672">*</span>args, <span style="color:#f92672">**</span>kwargs):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Insert a buy market order.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@abc.abstractmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">receiver_queue</span>(self) <span style="color:#f92672">-&gt;</span> asyncio<span style="color:#f92672">.</span>Queue:
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Return the queue containing the messages from the Exchange.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@abc.abstractmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">sell_market_order</span>(self, symbol: Symbol, amount: Decimal, <span style="color:#f92672">*</span>args, <span style="color:#f92672">**</span>kwargs):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Insert a sell market order.&#34;&#34;&#34;</span>
</span></span></code></pre></div><p>The <code>receiver_queue</code> method returns a <code>Queue</code> containing the messages received
from the exchange. <strong>Remember</strong> that the bot connects to the exchanges using
websockets. There will be a continuous flow of messages, which will be parsed by
the class and added to the <code>receiver_queue</code>.</p>
<p>I decided to implement only two exchanges: Binance and AVNU. The latter is not a
real DEX but an aggregator. However, it is useful because it offers an API that
provides prices and quotes for all the DEXes implemented.</p>
<h2 id="binance">Binance</h2>
<p>No need to introduce Binance. It is the centralised exchange with the highest
volume and market share. To handle the websocket,
<a href="https://github.com/ccxt/ccxt">ccxt</a> is used. The <code>Binance</code> class defined in
<code>exchange/cex/binance.py</code> is a wrapper around the ccxt methods.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Binance</span>(Exchange):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;Binance exchange.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, api_key: str, secret_key: str) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_exchange_handle <span style="color:#f92672">=</span> ccxt<span style="color:#f92672">.</span>pro<span style="color:#f92672">.</span>binance(
</span></span><span style="display:flex;"><span>            {<span style="color:#e6db74">&#34;apiKey&#34;</span>: api_key, <span style="color:#e6db74">&#34;secret&#34;</span>: secret_key}
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_receiver_queue <span style="color:#f92672">=</span> asyncio<span style="color:#f92672">.</span>Queue()
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span></code></pre></div><p>As you can see, <code>__init__</code> needs <code>api_key</code> and <code>secret_key</code> to authenticate in
the authenticated channel and to send orders. The class is very simple. The bot calls
<code>subscribe_ticker</code>, then ccxt sends the request to subscribe to the ticker
channel and <code>_handle_ticker</code> handles the messages.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_handle_ticker</span>(self, symbol: str):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Handle ticker messages and connection.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>            msg <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_exchange_handle<span style="color:#f92672">.</span>watch_ticker(symbol)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            ticker <span style="color:#f92672">=</span> Ticker(
</span></span><span style="display:flex;"><span>                msg,
</span></span><span style="display:flex;"><span>                Decimal(msg[<span style="color:#e6db74">&#34;info&#34;</span>][<span style="color:#e6db74">&#34;b&#34;</span>]),
</span></span><span style="display:flex;"><span>                Decimal(msg[<span style="color:#e6db74">&#34;info&#34;</span>][<span style="color:#e6db74">&#34;B&#34;</span>]),
</span></span><span style="display:flex;"><span>                Decimal(msg[<span style="color:#e6db74">&#34;info&#34;</span>][<span style="color:#e6db74">&#34;a&#34;</span>]),
</span></span><span style="display:flex;"><span>                Decimal(msg[<span style="color:#e6db74">&#34;info&#34;</span>][<span style="color:#e6db74">&#34;A&#34;</span>]),
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_receiver_queue<span style="color:#f92672">.</span>put(ticker)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">subscribe_ticker</span>(self, symbol: Symbol, <span style="color:#f92672">**</span>_):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Subscribe to the ticker.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        exchange_symbol <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>name<span style="color:#e6db74">}{</span>symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        asyncio<span style="color:#f92672">.</span>create_task(self<span style="color:#f92672">.</span>_handle_ticker(exchange_symbol))
</span></span></code></pre></div><p>The bot will also need to subscribe to the authenticated channel in the same way
as <code>_fetch_ticker</code>, so it can receive wallet updates.</p>
<p>The Binance websocket has one problem: it does not send initial snapshots of
both, the balances and the ticker. For the ticker, this isn&rsquo;t a big deal for
active pairs, as an update is sent immediately. However, for the balances, it is
an issue because an update is only sent when the balances change. This means an
initial request for the balances is required.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">subscribe_balance</span>(self, symbol: Symbol, <span style="color:#f92672">**</span>_):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Subscribe to the authenticated endpoint for balances.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        NOTE: Balances are filtered by `Symbol`.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        exchange_symbol <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>name<span style="color:#e6db74">}{</span>symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_receiver_queue(
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_exchange_handle<span style="color:#f92672">.</span>fetchBalance(exchange_symbol)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        asyncio<span style="color:#f92672">.</span>create_task(self<span style="color:#f92672">.</span>_handle_balance(exchange_symbol))
</span></span></code></pre></div><p>The method of sending an order is as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">buy_market_order</span>(
</span></span><span style="display:flex;"><span>        self, symbol: Symbol, amount: Decimal, <span style="color:#f92672">*</span>_, <span style="color:#f92672">**</span>__
</span></span><span style="display:flex;"><span>    ):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Insert a new buy market order.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        exchange_symbol <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>name<span style="color:#e6db74">}{</span>symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        order <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_exchange_handle<span style="color:#f92672">.</span>create_order_ws(
</span></span><span style="display:flex;"><span>            exchange_symbol, <span style="color:#e6db74">&#34;market&#34;</span>, <span style="color:#e6db74">&#34;buy&#34;</span>, float(amount), params<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;test&#34;</span>: <span style="color:#66d9ef">True</span>}
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_receiver_queue(Order(
</span></span><span style="display:flex;"><span>            order, symbol, Decimal(str(order<span style="color:#f92672">.</span>price)), Decimal(str(order<span style="color:#f92672">.</span>amount)), <span style="color:#e6db74">&#34;buy&#34;</span>
</span></span><span style="display:flex;"><span>        ))
</span></span></code></pre></div><h2 id="avnu">AVNU</h2>
<p>As aforementioned, AVNU is not a real DEX, it is an aggregator. I use this
protocol because its API provides prices and quotes from all supported
exchanges. At the expense of higher fees, the bot can easily swap with <a href="https://starknet.api.avnu.fi/swap/v2/prices">10
different DEXes</a>.</p>
<p>There is only one problem with the API: it does not offer a websocket
connection, so the class needs to simulate it. <strong>Remember that I want to
abstract this problem to <code>Arbitrage</code></strong>. The bot expects to receive a stream of
messages, not to make requests.The main idea is to create coroutines that make
requests every $$N$$ seconds. But first let&rsquo;s define some useful constants for
the class:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>ETH <span style="color:#f92672">=</span> Token(
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;ETH&#34;</span>, <span style="color:#e6db74">&#34;0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7&#34;</span>, <span style="color:#ae81ff">18</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>URLS <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;base&#34;</span>: <span style="color:#e6db74">&#34;https://starknet.api.avnu.fi&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;quotes&#34;</span>: <span style="color:#e6db74">&#34;swap/v2/quotes&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;prices&#34;</span>: <span style="color:#e6db74">&#34;swap/v2/prices&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;sources&#34;</span>: <span style="color:#e6db74">&#34;swap/v2/sources&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;build&#34;</span>: <span style="color:#e6db74">&#34;swap/v2/build&#34;</span>,
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>ETH</code> is a <code>Token</code> object representing the Ethereum token on Starknet, while
<code>URLS</code> is a dictionary containing the AVNU API url and endpoints. <code>AVNU</code> is
defined as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">AVNU</span>(Exchange):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;AVNU exchange.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, account: Account, balance: Symbol) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_account <span style="color:#f92672">=</span> account
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Fetch available dexes</span>
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>URLS[<span style="color:#e6db74">&#39;base&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>URLS[<span style="color:#e6db74">&#39;sources&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        available_dexes <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>            dex[<span style="color:#e6db74">&#34;name&#34;</span>] <span style="color:#66d9ef">for</span> dex <span style="color:#f92672">in</span> response<span style="color:#f92672">.</span>json() <span style="color:#66d9ef">if</span> dex[<span style="color:#e6db74">&#34;type&#34;</span>] <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;DEX&#34;</span>
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>        available_dexes<span style="color:#f92672">.</span>append(<span style="color:#e6db74">&#34;lastPrice&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_last_prices <span style="color:#f92672">=</span> {dex: Decimal(<span style="color:#e6db74">&#34;0&#34;</span>) <span style="color:#66d9ef">for</span> dex <span style="color:#f92672">in</span> available_dexes}
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_receiver_queue <span style="color:#f92672">=</span> asyncio<span style="color:#f92672">.</span>Queue()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        asyncio<span style="color:#f92672">.</span>create_task(self<span style="color:#f92672">.</span>_fetch_balance(balance))
</span></span></code></pre></div><p>As you can see, the class starts by fetching the exchanges supported by AVNU and
initialising <code>self._last_prices</code>. The request is synchronous because:</p>
<ul>
<li><code>__init__</code> cannot be asynchronous. This is a limitation of Python, the class
initialiser cannot be marked as <code>async</code></li>
<li>even if it is a blocking request, it is fine to make this call while
initialising</li>
</ul>
<p>Finally, it creates the coroutine for <code>_fetch_balance</code> which fetches the initial
account balances. The method is defined as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_fetch_balance</span>(self, symbol: typing<span style="color:#f92672">.</span>Optional[Symbol] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>):
</span></span><span style="display:flex;"><span>        tokens <span style="color:#f92672">=</span> [ETH] <span style="color:#66d9ef">if</span> symbol <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span> <span style="color:#66d9ef">else</span> [symbol<span style="color:#f92672">.</span>base, symbol<span style="color:#f92672">.</span>quote]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> token <span style="color:#f92672">in</span> tokens:
</span></span><span style="display:flex;"><span>            logger<span style="color:#f92672">.</span>debug(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Fetching </span><span style="color:#e6db74">{</span>token<span style="color:#f92672">.</span>address<span style="color:#e6db74">}</span><span style="color:#e6db74"> balance&#34;</span>)
</span></span><span style="display:flex;"><span>            amount <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_account<span style="color:#f92672">.</span>get_balance(token<span style="color:#f92672">.</span>address)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_receiver_queue<span style="color:#f92672">.</span>put(
</span></span><span style="display:flex;"><span>                Wallet({<span style="color:#e6db74">&#34;amount&#34;</span>: amount}, token, Decimal(amount) <span style="color:#f92672">/</span> <span style="color:#ae81ff">10</span><span style="color:#f92672">**</span>token<span style="color:#f92672">.</span>decimals)
</span></span><span style="display:flex;"><span>            )
</span></span></code></pre></div><p>Let&rsquo;s see how <code>subscribe_ticker</code> is implemented:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">subscribe_ticker</span>(self, symbol: Symbol, amount: decimal<span style="color:#f92672">.</span>Decimal, <span style="color:#f92672">**</span>_):
</span></span><span style="display:flex;"><span>        asyncio<span style="color:#f92672">.</span>create_task(self<span style="color:#f92672">.</span>_handle_quotes(symbol, amount))
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_handle_quotes</span>(
</span></span><span style="display:flex;"><span>        self,
</span></span><span style="display:flex;"><span>        symbol: Symbol,
</span></span><span style="display:flex;"><span>        amount: decimal<span style="color:#f92672">.</span>Decimal,
</span></span><span style="display:flex;"><span>    ):
</span></span><span style="display:flex;"><span>        url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>URLS[<span style="color:#e6db74">&#39;base&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>URLS[<span style="color:#e6db74">&#39;quotes&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        params_sell <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;sellAmount&#34;</span>: hex(amount <span style="color:#f92672">*</span> <span style="color:#ae81ff">10</span> <span style="color:#f92672">**</span> int(symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>decimals)),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;sellTokenAddress&#34;</span>: symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>address,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;buyTokenAddress&#34;</span>: symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>address,
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        params_buy <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;sellAmount&#34;</span>: hex(amount <span style="color:#f92672">*</span> <span style="color:#ae81ff">10</span> <span style="color:#f92672">**</span> int(symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>decimals)),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;sellTokenAddress&#34;</span>: symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>address,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;buyTokenAddress&#34;</span>: symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>address,
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">with</span> aiohttp<span style="color:#f92672">.</span>ClientSession() <span style="color:#66d9ef">as</span> session:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>                logger<span style="color:#f92672">.</span>debug(<span style="color:#e6db74">&#34;Fetching quotes&#34;</span>)
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># We need to make two requests because a call to `/quotes` also</span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># sets the swap order. Therefore, if we want to buy `base` from</span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># `quote` we need the opposite ordeer (and a new `quoteId`)</span>
</span></span><span style="display:flex;"><span>                resp_sell, resp_buy <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>gather(
</span></span><span style="display:flex;"><span>                    session<span style="color:#f92672">.</span>get(url, params<span style="color:#f92672">=</span>params_sell),
</span></span><span style="display:flex;"><span>                    session<span style="color:#f92672">.</span>get(url, params<span style="color:#f92672">=</span>params_buy),
</span></span><span style="display:flex;"><span>                )
</span></span><span style="display:flex;"><span>                entries_sell, entries_buy <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>gather(
</span></span><span style="display:flex;"><span>                    resp_sell<span style="color:#f92672">.</span>json(), resp_buy<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>                )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># `entries_sell` is a list with one element</span>
</span></span><span style="display:flex;"><span>                entry <span style="color:#f92672">=</span> entries_sell[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                quote_amount <span style="color:#f92672">=</span> decimal<span style="color:#f92672">.</span>Decimal(int(entry[<span style="color:#e6db74">&#34;buyAmount&#34;</span>], <span style="color:#ae81ff">16</span>))
</span></span><span style="display:flex;"><span>                quote_amount <span style="color:#f92672">=</span> quote_amount <span style="color:#f92672">/</span> (<span style="color:#ae81ff">10</span><span style="color:#f92672">**</span>symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>decimals)
</span></span><span style="display:flex;"><span>                price <span style="color:#f92672">=</span> quote_amount <span style="color:#f92672">/</span> amount
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>_last_prices[<span style="color:#e6db74">&#34;lastPrice&#34;</span>] <span style="color:#f92672">!=</span> price:
</span></span><span style="display:flex;"><span>                    self<span style="color:#f92672">.</span>_last_prices[<span style="color:#e6db74">&#34;lastPrice&#34;</span>] <span style="color:#f92672">=</span> price
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    entry[<span style="color:#e6db74">&#34;sellId&#34;</span>] <span style="color:#f92672">=</span> entry[<span style="color:#e6db74">&#34;quoteId&#34;</span>]
</span></span><span style="display:flex;"><span>                    entry[<span style="color:#e6db74">&#34;buyId&#34;</span>] <span style="color:#f92672">=</span> entries_buy[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;quoteId&#34;</span>]
</span></span><span style="display:flex;"><span>                    ticker <span style="color:#f92672">=</span> Ticker(entry, price, amount, price, amount)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_receiver_queue<span style="color:#f92672">.</span>put(ticker)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>The methods are simple. The first creates a coroutine to <code>_handle_quotes</code>, while
the second does the actual work . Rather than using the <code>prices</code> endpoint,
<code>quotes</code> is used. Not only to get the best price, but to ask to AVNU to prepare
the swap for us. The
<a href="https://doc.avnu.fi/integration/api-references#/swap-v2-quotes">endpoint</a>
builds a route to get the best swap and assigns a unique <code>quoteId</code> to each
request. The id can be used later when the class sends the request to build the
swap transaction. Two requests are made because it is not possible to change the
order of the buy/sell token addresses while the transaction is being built.</p>
<p>The method continues with parsing the outputs, calculating the price, storing
the <code>quoteId</code> and adding the <code>Ticker</code> to the <code>receiver_queue</code>.</p>
<p>The last methods are for sending the orders:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">buy_market_order</span>(
</span></span><span style="display:flex;"><span>        self,
</span></span><span style="display:flex;"><span>        symbol: Symbol,
</span></span><span style="display:flex;"><span>        amount: Decimal,
</span></span><span style="display:flex;"><span>        ticker: Ticker,
</span></span><span style="display:flex;"><span>        slippage: Decimal <span style="color:#f92672">=</span> Decimal(<span style="color:#e6db74">&#34;0.01&#34;</span>),
</span></span><span style="display:flex;"><span>    ):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Insert a buy market order.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>URLS[<span style="color:#e6db74">&#39;base&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>URLS[<span style="color:#e6db74">&#39;build&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        payload <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;slippage&#34;</span>: str(slippage),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;takerAddress&#34;</span>: hex(self<span style="color:#f92672">.</span>_account<span style="color:#f92672">.</span>address),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;quoteId&#34;</span>: ticker<span style="color:#f92672">.</span>raw[<span style="color:#e6db74">&#34;quoteId&#34;</span>],
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;includeApprove&#34;</span>: <span style="color:#66d9ef">True</span>,
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># FIXME: Share session with `handle_prices_quotes`</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">with</span> aiohttp<span style="color:#f92672">.</span>ClientSession() <span style="color:#66d9ef">as</span> session:
</span></span><span style="display:flex;"><span>            response <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> session<span style="color:#f92672">.</span>post(url, json<span style="color:#f92672">=</span>payload)
</span></span><span style="display:flex;"><span>            resp_calls <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> response<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># `resp_calls` is a dict containing `chainId` and `calls` keys. A</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># call is a dict containing `contractAddress, entrypoint` and</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># `calldata`. Values are hexed or string (the selector)</span>
</span></span><span style="display:flex;"><span>            calls <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>                Call(
</span></span><span style="display:flex;"><span>                    int(call[<span style="color:#e6db74">&#34;contractAddress&#34;</span>], <span style="color:#ae81ff">16</span>),
</span></span><span style="display:flex;"><span>                    get_selector_from_name(call[<span style="color:#e6db74">&#34;entrypoint&#34;</span>]),
</span></span><span style="display:flex;"><span>                    [int(call_value, <span style="color:#ae81ff">16</span>) <span style="color:#66d9ef">for</span> call_value <span style="color:#f92672">in</span> call[<span style="color:#e6db74">&#34;calldata&#34;</span>]],
</span></span><span style="display:flex;"><span>                )
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">for</span> call <span style="color:#f92672">in</span> resp_calls[<span style="color:#e6db74">&#34;calls&#34;</span>]
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;sending order&#34;</span>)
</span></span><span style="display:flex;"><span>            transaction_hash <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_account<span style="color:#f92672">.</span>execute_v3(calls, auto_estimate<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_wait_txn(
</span></span><span style="display:flex;"><span>                transaction_hash<span style="color:#f92672">.</span>transaction_hash, symbol, amount, ticker, <span style="color:#e6db74">&#34;buy&#34;</span>
</span></span><span style="display:flex;"><span>            )
</span></span></code></pre></div><p>At this stage <code>build</code> endpoint is used to build the swap transaction. In the
future I would like to remove AVNU and swap directly on the DEXes.</p>
<p>The <code>quoteId</code> is used to request AVNU to build the swap and return the
transaction calldata. The response is prepared for
<a href="https://www.starknetjs.com/">Starknetjs</a> and not for the python version.
<code>contractAddress</code> and <code>calldata</code> need to be converted to <code>int</code> while
<code>entrypoint</code> is a human readable string and needs to be converted into the
selector (i.e. from <code>approve</code> to
<code>0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f</code>).</p>
<p>The final steps are to broadcast the transaction and wait for the sequencer to
validate it. Once validated, the <code>Order</code> message is added to the
<code>receiver_queue</code> and the new balances are fetched:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_wait_txn</span>(
</span></span><span style="display:flex;"><span>        self,
</span></span><span style="display:flex;"><span>        transaction_hash: int,
</span></span><span style="display:flex;"><span>        symbol: Symbol,
</span></span><span style="display:flex;"><span>        amount: Decimal,
</span></span><span style="display:flex;"><span>        ticker: Ticker,
</span></span><span style="display:flex;"><span>        side: str,
</span></span><span style="display:flex;"><span>    ):
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Wait until transaction is confirmed. After create the order and update the balance.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_account<span style="color:#f92672">.</span>client<span style="color:#f92672">.</span>wait_for_tx(transaction_hash)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        order <span style="color:#f92672">=</span> Order(
</span></span><span style="display:flex;"><span>            {<span style="color:#e6db74">&#34;transaction_hash&#34;</span>: transaction_hash},
</span></span><span style="display:flex;"><span>            symbol,
</span></span><span style="display:flex;"><span>            amount,
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># NOTE: `ask` and `bid` are the same</span>
</span></span><span style="display:flex;"><span>            ticker<span style="color:#f92672">.</span>ask,
</span></span><span style="display:flex;"><span>            side,
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_receiver_queue<span style="color:#f92672">.</span>put(order)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        asyncio<span style="color:#f92672">.</span>create_task(self<span style="color:#f92672">.</span>_fetch_balance(symbol))
</span></span></code></pre></div><h1 id="arbitrage-class">Arbitrage class</h1>
<p>In the previous sections, I defined the <code>Exchange</code> interface and described how I
implemented <code>Binance</code> and <code>AVNU</code>. In this section, I will describe the last
component of the bot: the <code>Arbitrage</code> class, the most important one. It is
responsible for determining if there is a profit and attempting to take it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Arbitrage</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(
</span></span><span style="display:flex;"><span>        self,
</span></span><span style="display:flex;"><span>        exchanges: list[Exchange],
</span></span><span style="display:flex;"><span>        symbol: Symbol,
</span></span><span style="display:flex;"><span>        spread_threshold: Decimal,
</span></span><span style="display:flex;"><span>        trade_amount: Decimal,
</span></span><span style="display:flex;"><span>        min_trade_amount: Decimal,
</span></span><span style="display:flex;"><span>    ):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_exchanges <span style="color:#f92672">=</span> exchanges
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_symbol <span style="color:#f92672">=</span> symbol
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_threshold <span style="color:#f92672">=</span> spread_threshold
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_queue: asyncio<span style="color:#f92672">.</span>Queue[tuple[Any, Exchange]] <span style="color:#f92672">=</span> asyncio<span style="color:#f92672">.</span>Queue()
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_trade_amount <span style="color:#f92672">=</span> trade_amount
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>_min_trade_amount <span style="color:#f92672">=</span> min_trade_amount
</span></span></code></pre></div><p>It requires a list of <code>Exchange</code>, the <code>Symbol</code> on which to trade,
<code>spread_threshold</code> which indicates the profit threshold, and <code>trade_amount</code> and
<code>min_trade_amount</code> which define the trading range.</p>
<p>The more observant will have noticed that <code>Arbitrage</code> requires a list of
<code>Exchange</code> rather than a single CEX and DEX. The class is capable of iterate
over multiple exchanges and it does not care whether the exchanges are CEXes or
DEXes.</p>
<p><code>Arbitrage</code> defines two utility functions, <code>_initialize</code> and <code>_merge_queues</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_initialize</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> exchange <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>_exchanges:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">await</span> exchange<span style="color:#f92672">.</span>subscribe_ticker(
</span></span><span style="display:flex;"><span>                self<span style="color:#f92672">.</span>_symbol, amount<span style="color:#f92672">=</span>int(self<span style="color:#f92672">.</span>_trade_amount)
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>            queue <span style="color:#f92672">=</span> exchange<span style="color:#f92672">.</span>receiver_queue()
</span></span><span style="display:flex;"><span>            asyncio<span style="color:#f92672">.</span>create_task(self<span style="color:#f92672">.</span>_merge_queues(queue, exchange))
</span></span></code></pre></div><p>As the name suggests, <code>_initialize</code> is called to subscribe to the tickers and to
merge the queues into a single one by calling <code>_merge_queues</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_merge_queues</span>(self, queue: asyncio<span style="color:#f92672">.</span>Queue, ex: Exchange):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> queue<span style="color:#f92672">.</span>get()
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_queue<span style="color:#f92672">.</span>put((val, ex))
</span></span></code></pre></div><p>The functions for calculating the spread and the amount to be swapped are quite
simple:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_calculate_spread</span>(self, ask: Decimal, bid: Decimal) <span style="color:#f92672">-&gt;</span> Decimal:
</span></span><span style="display:flex;"><span>        numerator <span style="color:#f92672">=</span> bid <span style="color:#f92672">-</span> ask
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> numerator <span style="color:#f92672">/</span> bid
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_calculate_amount</span>(
</span></span><span style="display:flex;"><span>        self,
</span></span><span style="display:flex;"><span>        ask: Ticker,
</span></span><span style="display:flex;"><span>        bid: Ticker,
</span></span><span style="display:flex;"><span>        wallet_ask: Decimal,
</span></span><span style="display:flex;"><span>        wallet_bid: Decimal,
</span></span><span style="display:flex;"><span>    ) <span style="color:#f92672">-&gt;</span> Decimal:
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;&#34;&#34;Return the amount we can trade.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        amount <span style="color:#f92672">=</span> min(self<span style="color:#f92672">.</span>_trade_amount, ask<span style="color:#f92672">.</span>ask_amount, bid<span style="color:#f92672">.</span>bid_amount)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Check wallets have enough balance</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># NOTE: `wallet_ask` is the quote token balance. Convert in base</span>
</span></span><span style="display:flex;"><span>        amount <span style="color:#f92672">=</span> min(amount, wallet_bid, wallet_ask <span style="color:#f92672">*</span> ask<span style="color:#f92672">.</span>ask)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> amount
</span></span></code></pre></div><p>As you can see, <code>_calculate_amount</code>, follows the formula described in <a href="https://lisot.to/posts/starknet-arbitrage-basics/#step-3-sending-orders">Step 3:
Sending orders</a>.
Remember that <code>wallet_ask</code> is the total amount of available <code>quote</code> tokens. A
<code>base</code> conversion is needed to compare them.</p>
<p>Finally, the last and the most interesting method of the class. The method waits
for messages from the exchanges and it decides to send the order if there is a
profit.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">run</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Subscribe to the tickers and merge the queues</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_initialize()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        best_bid <span style="color:#f92672">=</span> Ticker({}, Decimal(<span style="color:#e6db74">&#34;-INFINITY&#34;</span>), Decimal(<span style="color:#ae81ff">0</span>), Decimal(<span style="color:#ae81ff">0</span>), Decimal(<span style="color:#ae81ff">0</span>))
</span></span><span style="display:flex;"><span>        best_ask <span style="color:#f92672">=</span> Ticker({}, Decimal(<span style="color:#ae81ff">0</span>), Decimal(<span style="color:#ae81ff">0</span>), Decimal(<span style="color:#e6db74">&#34;INFINITY&#34;</span>), Decimal(<span style="color:#ae81ff">0</span>))
</span></span><span style="display:flex;"><span>        exchange_ask <span style="color:#f92672">=</span> exchange_bid <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>_exchanges[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        wallets <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            exchange: {
</span></span><span style="display:flex;"><span>                self<span style="color:#f92672">.</span>_symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>name: Wallet<span style="color:#f92672">.</span>empty(self<span style="color:#f92672">.</span>_symbol<span style="color:#f92672">.</span>base),
</span></span><span style="display:flex;"><span>                self<span style="color:#f92672">.</span>_symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>name: Wallet<span style="color:#f92672">.</span>empty(self<span style="color:#f92672">.</span>_symbol<span style="color:#f92672">.</span>quote),
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> exchange <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>_exchanges
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>            msg, exchange <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> self<span style="color:#f92672">.</span>_queue<span style="color:#f92672">.</span>get()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">match</span> msg:
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">case</span> Wallet():
</span></span><span style="display:flex;"><span>                    wallets[exchange][msg<span style="color:#f92672">.</span>token<span style="color:#f92672">.</span>name] <span style="color:#f92672">=</span> msg
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">case</span> Order():
</span></span><span style="display:flex;"><span>                    logger<span style="color:#f92672">.</span>info(
</span></span><span style="display:flex;"><span>                        <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>exchange<span style="color:#e6db74">}</span><span style="color:#e6db74"> order executed. </span><span style="color:#e6db74">{</span>msg<span style="color:#f92672">.</span>side<span style="color:#e6db74">}</span><span style="color:#e6db74">: </span><span style="color:#e6db74">{</span>msg<span style="color:#f92672">.</span>amount<span style="color:#e6db74">}</span><span style="color:#e6db74"> at </span><span style="color:#e6db74">{</span>msg<span style="color:#f92672">.</span>price<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>                    )
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">case</span> Ticker():
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e"># We want to buy at the lowest price</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> msg<span style="color:#f92672">.</span>ask <span style="color:#f92672">&lt;=</span> best_ask<span style="color:#f92672">.</span>ask:
</span></span><span style="display:flex;"><span>                        best_ask <span style="color:#f92672">=</span> msg
</span></span><span style="display:flex;"><span>                        exchange_ask <span style="color:#f92672">=</span> exchange
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e"># We want to sell at the highest price</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> msg<span style="color:#f92672">.</span>bid <span style="color:#f92672">&gt;=</span> best_bid<span style="color:#f92672">.</span>bid:
</span></span><span style="display:flex;"><span>                        best_bid <span style="color:#f92672">=</span> msg
</span></span><span style="display:flex;"><span>                        exchange_bid <span style="color:#f92672">=</span> exchange
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e"># Same exchange, skip</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> exchange_bid <span style="color:#f92672">==</span> exchange_ask:
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    spread <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>_calculate_spread(
</span></span><span style="display:flex;"><span>                        best_ask<span style="color:#f92672">.</span>ask,
</span></span><span style="display:flex;"><span>                        best_bid<span style="color:#f92672">.</span>bid,
</span></span><span style="display:flex;"><span>                    )
</span></span><span style="display:flex;"><span>                    ba <span style="color:#f92672">=</span> dataclasses<span style="color:#f92672">.</span>replace(best_ask)
</span></span><span style="display:flex;"><span>                    ba<span style="color:#f92672">.</span>raw <span style="color:#f92672">=</span> exchange_ask
</span></span><span style="display:flex;"><span>                    bb <span style="color:#f92672">=</span> dataclasses<span style="color:#f92672">.</span>replace(best_bid)
</span></span><span style="display:flex;"><span>                    bb<span style="color:#f92672">.</span>raw <span style="color:#f92672">=</span> exchange_bid
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    logger<span style="color:#f92672">.</span>debug(
</span></span><span style="display:flex;"><span>                        <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;spread: </span><span style="color:#e6db74">{</span>spread<span style="color:#e6db74">}</span><span style="color:#e6db74"> best ask: </span><span style="color:#e6db74">{</span>ba<span style="color:#f92672">.</span>ask<span style="color:#e6db74">}</span><span style="color:#e6db74"> best bid: </span><span style="color:#e6db74">{</span>bb<span style="color:#f92672">.</span>bid<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>                    )
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> spread <span style="color:#f92672">&gt;=</span> self<span style="color:#f92672">.</span>_threshold:
</span></span><span style="display:flex;"><span>                        logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>spread<span style="color:#e6db74">}</span><span style="color:#e6db74"> above the threshdold&#34;</span>)
</span></span><span style="display:flex;"><span>                        amount <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>_calculate_amount(
</span></span><span style="display:flex;"><span>                            best_ask,
</span></span><span style="display:flex;"><span>                            best_bid,
</span></span><span style="display:flex;"><span>                            wallets[exchange_ask][self<span style="color:#f92672">.</span>_symbol<span style="color:#f92672">.</span>base<span style="color:#f92672">.</span>name]<span style="color:#f92672">.</span>amount,
</span></span><span style="display:flex;"><span>                            wallets[exchange_bid][self<span style="color:#f92672">.</span>_symbol<span style="color:#f92672">.</span>quote<span style="color:#f92672">.</span>name]<span style="color:#f92672">.</span>amount,
</span></span><span style="display:flex;"><span>                        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">if</span> amount <span style="color:#f92672">&lt;</span> self<span style="color:#f92672">.</span>_min_trade_amount:
</span></span><span style="display:flex;"><span>                            <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                        logger<span style="color:#f92672">.</span>debug(
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>exchange_ask<span style="color:#e6db74">}</span><span style="color:#e6db74">: buy: </span><span style="color:#e6db74">{</span>amount<span style="color:#e6db74">}</span><span style="color:#e6db74"> price: </span><span style="color:#e6db74">{</span>best_ask<span style="color:#f92672">.</span>ask<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>                        )
</span></span><span style="display:flex;"><span>                        logger<span style="color:#f92672">.</span>debug(
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>exchange_bid<span style="color:#e6db74">}</span><span style="color:#e6db74">: sell: </span><span style="color:#e6db74">{</span>amount<span style="color:#e6db74">}</span><span style="color:#e6db74"> price: </span><span style="color:#e6db74">{</span>best_bid<span style="color:#f92672">.</span>bid<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>                        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>gather(
</span></span><span style="display:flex;"><span>                            exchange_ask<span style="color:#f92672">.</span>buy_market_order(
</span></span><span style="display:flex;"><span>                                self<span style="color:#f92672">.</span>_symbol, amount, best_ask
</span></span><span style="display:flex;"><span>                            ),
</span></span><span style="display:flex;"><span>                            exchange_bid<span style="color:#f92672">.</span>sell_market_order(
</span></span><span style="display:flex;"><span>                                self<span style="color:#f92672">.</span>_symbol, amount, best_bid
</span></span><span style="display:flex;"><span>                            ),
</span></span><span style="display:flex;"><span>                        )
</span></span></code></pre></div><h1 id="almost-ready">Almost ready</h1>
<p>The bot is almost ready. What is missing is loading the config and the <code>main</code>
function. The config is in an <code>.env</code> file and is read using the
<a href="https://pypi.org/project/python-dotenv/">dotenv</a> library. <code>Config</code> is
responsible for loading and validating the configuration.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Config</span>:
</span></span><span style="display:flex;"><span>    REQUIRED_KEYS <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;BASE_ADDR&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;BASE_DECIMALS&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;BASE&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;QUOTE_ADDR&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;QUOTE_DECIMALS&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;QUOTE&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;API_KEY&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;SECRET_KEY&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;SIGNER_KEY&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;NODE_URL&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;SPREAD_THRESHOLD&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;MAX_AMOUNT_TRADE&#34;</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, config: dict) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> key <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>REQUIRED_KEYS:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> key <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> config:
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">raise</span> ValidationError(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Missing `</span><span style="color:#e6db74">{</span>key<span style="color:#e6db74">}</span><span style="color:#e6db74">`&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>base <span style="color:#f92672">=</span> Token(
</span></span><span style="display:flex;"><span>            config[<span style="color:#e6db74">&#34;BASE&#34;</span>], config[<span style="color:#e6db74">&#34;BASE_ADDR&#34;</span>], int(config[<span style="color:#e6db74">&#34;BASE_DECIMALS&#34;</span>])
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>quote <span style="color:#f92672">=</span> Token(
</span></span><span style="display:flex;"><span>            config[<span style="color:#e6db74">&#34;QUOTE&#34;</span>], config[<span style="color:#e6db74">&#34;QUOTE_ADDR&#34;</span>], int(config[<span style="color:#e6db74">&#34;QUOTE_DECIMALS&#34;</span>])
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>symbol <span style="color:#f92672">=</span> Symbol(self<span style="color:#f92672">.</span>base, self<span style="color:#f92672">.</span>quote)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>api_key <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;API_KEY&#34;</span>]
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>secret_key <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;SECRET_KEY&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>node_url <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;NODE_URL&#34;</span>]
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>account_address <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;ACCOUNT_ADDRESS&#34;</span>]
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>signer_key <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;SIGNER_KEY&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>spread_threshold <span style="color:#f92672">=</span> Decimal(config[<span style="color:#e6db74">&#34;SPREAD_THRESHOLD&#34;</span>])
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>max_amount_trade <span style="color:#f92672">=</span> Decimal(config[<span style="color:#e6db74">&#34;MAX_AMOUNT_TRADE&#34;</span>])
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>min_amount_trade <span style="color:#f92672">=</span> Decimal(config[<span style="color:#e6db74">&#34;MIN_AMOUNT_TRADE&#34;</span>])
</span></span></code></pre></div><p>Finally, the <code>main</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Bot settings</span>
</span></span><span style="display:flex;"><span>    logger <span style="color:#f92672">=</span> logging<span style="color:#f92672">.</span>getLogger(<span style="color:#e6db74">&#34;bot&#34;</span>)
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>setLevel(logging<span style="color:#f92672">.</span>DEBUG)
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>addHandler(RichHandler())
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Load config</span>
</span></span><span style="display:flex;"><span>    config <span style="color:#f92672">=</span> {<span style="color:#f92672">**</span>dotenv_values(<span style="color:#e6db74">&#34;.env&#34;</span>), <span style="color:#f92672">**</span>os<span style="color:#f92672">.</span>environ}
</span></span><span style="display:flex;"><span>    config <span style="color:#f92672">=</span> Config(config)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Exchange initialisation</span>
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>debug(<span style="color:#e6db74">&#34;Connecting to starknet...&#34;</span>)
</span></span><span style="display:flex;"><span>    chain <span style="color:#f92672">=</span> Starknet(config<span style="color:#f92672">.</span>node_url)
</span></span><span style="display:flex;"><span>    account <span style="color:#f92672">=</span> chain<span style="color:#f92672">.</span>get_account(config<span style="color:#f92672">.</span>account_address, config<span style="color:#f92672">.</span>signer_key)
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>debug(<span style="color:#e6db74">&#34;Connecting to AVNU...&#34;</span>)
</span></span><span style="display:flex;"><span>    avnu <span style="color:#f92672">=</span> AVNU(account, config<span style="color:#f92672">.</span>symbol)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>debug(<span style="color:#e6db74">&#34;Connecting to binance...&#34;</span>)
</span></span><span style="display:flex;"><span>    binance <span style="color:#f92672">=</span> Binance(config<span style="color:#f92672">.</span>api_key, config<span style="color:#f92672">.</span>secret_key)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Run bot</span>
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>debug(<span style="color:#e6db74">&#34;All setup, running bot...&#34;</span>)
</span></span><span style="display:flex;"><span>    bot <span style="color:#f92672">=</span> Arbitrage(
</span></span><span style="display:flex;"><span>        [binance, avnu],
</span></span><span style="display:flex;"><span>        config<span style="color:#f92672">.</span>symbol,
</span></span><span style="display:flex;"><span>        config<span style="color:#f92672">.</span>spread_threshold,
</span></span><span style="display:flex;"><span>        config<span style="color:#f92672">.</span>max_amount_trade,
</span></span><span style="display:flex;"><span>        config<span style="color:#f92672">.</span>min_amount_trade,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">await</span> bot<span style="color:#f92672">.</span>run()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>asyncio<span style="color:#f92672">.</span>run(main())
</span></span></code></pre></div><h1 id="future-improvements">Future improvements</h1>
<p>The bot explained is very basic and there is room for many improvements. Here
are a few:</p>
<ul>
<li>Clean up the code and build more robust components. For example, if a
coroutine crashes, the bot continues to work without that coroutine. This is a
big problem if the crashed coroutine is the function that parses the tickers.</li>
<li>Remove <code>ccxt</code>.While the library is great for prototyping, it introduces a lot
of unnecessary requests and is slow. It can be replaced by calling the Binance
API.</li>
<li>Replace <code>AVNU</code>. Although is great for its API, the bot swaps at the best rates
while it should swap between the best and worst prices.</li>
<li>Avoid using <code>auto_estimate</code> when signing the transaction.</li>
</ul>
<p><em>As I mentioned in my previous articles of the series there is a group to discuss
MEV on Starknet. If you are interested in the argument, want to discuss or have
some interesting information, you can find me there. <a href="https://t.me/+TiNIOKAdIyQzNDg0">Feel free to join the
group</a>.</em></p>
]]></description>
      
    </item>
    
    
    
    <item>
      <title>Building an Arbitrage Bot on Starknet: Part 1 - The basics</title>
      <link>https://lisot.to/posts/starknet-arbitrage-basics/</link>
      <pubDate>Thu, 08 Aug 2024 11:21:26 +0200</pubDate>
      
      <guid>https://lisot.to/posts/starknet-arbitrage-basics/</guid>
      <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>This article is part of a series on building an arbitrage bot between
centralised (CEX) and decentralised (DEX) exchanges on Starknet.</p>
<p>In this article I will cover the functionalities of the bot, explaining its
functionality, how it works, the library used, and the underlying mathematics.</p>
<ul>
<li><a href="https://lisot.to/posts/starknet-arbitrage-fundamentals/">Building an Arbitrage Bot on Starknet: Part 0 - Understanding the
Fundamentals</a></li>
<li>Building an Arbitrage Bot on Starknet: Part 1 - The basics</li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-bot/">Building an Arbitrage Bot on Starknet: Part 2 - The bot</a></li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-conclusions/">Building an Arbitrage Bot on Starknet: Part 3 - Conclusions</a></li>
</ul>
<h1 id="what-is-an-arbitrage-bot">What is an arbitrage bot?</h1>
<p>There are many resources describing what an
<a href="https://www.investopedia.com/terms/a/arbitrageur.asp#:~:text=An%20arbitrageur%20is%20an%20investor,it%20at%20a%20higher%20price.">arbitrageur</a>
is and what arbitrage bots do. However, I will try to give my own perspective on
the subject.</p>
<p>In a market where many different entities offer services for the exchange of
assets, these assets may have different prices, even if they are the same. This
is because each service operates separately and users determine the price
through their behaviour.</p>
<p>Popular services attract more users, which means more liquidity and a more
stable price. On the other hand, services with less liquidity may have a more
volatile price. Fewer trades also update the price more slowly. These
differences are known as market inefficiencies. <strong>An arbitrageur is an entity
that tries to make a profit from these inefficiencies</strong> by buying the asset at
a lower price and selling it at a higher price.</p>
<p>Arbitrageurs do not exist only in the crypto space but are also a fundamental
part of traditional finance.</p>
<h2 id="cex-vs-dex">CEX vs DEX</h2>
<p>In the blockchain space, we have two main types of exchanges: centralised and
decentralised. A centralised exchange (CEX) is basically a trading platform run
by one company that lets people buy and sell crypto assets. They usually have an
order book where users can enter their orders and the engine will execute them.</p>
<p>A decentralised exchange (DEX) lives on a blockchain in the form of smart
contracts, sometimes regulated by a
<a href="https://en.wikipedia.org/wiki/Decentralized_autonomous_organization">DAO</a>. They
are a backbone of decentralised finance (DeFi). Automated Market Makers (AMMs)
are a type of DEX that use a mathematical formula to determine the price. Unlike
CEXes, AMMs consist of liquidity pools, typically with two or three crypto
assets. The most popular liquidity pools are <a href="https://blog.uniswap.org/uniswap-v2">Uniswap
V2</a> that follows the formula $$x * y = k$$
or <a href="https://blog.uniswap.org/uniswap-v3">Uniswap V3 with concentrated
liquidity</a>.</p>
<p>DeFi products are not just AMMs. The most famous order book product is probably
<a href="https://dydx.exchange/">DyDx</a>.</p>
<h2 id="risks">Risks</h2>
<p>An arbitrage bot has two big risks:</p>
<ul>
<li>Token exposure or inventory risk</li>
<li>Failed swaps</li>
</ul>
<h3 id="token-exposure">Token exposure</h3>
<p>The risk is not related to the bot, but with the tokens themselves. An arbitrage
bot is not interested in the value of the token it is trading. The problem lies
in the value of the tokens, if any go down in value, the profits will also go
down, reducing the profitability of the bot. The only solution is not to trade
very risky pairs or to sell the profits immediately.</p>
<h3 id="failed-swaps">Failed swaps</h3>
<p>Trades are not atomic, so one of them may be executed at a difference price or
fail. In addition, centralised exchanges may also have hidden orders or may not
execute (unless the market type is not selected).</p>
<p>For DEXes it is even more complicated:</p>
<ul>
<li>Bots must constantly monitor the mempool to see if there are trades that
change the price pool, but with private or encrypted mempool this is no longer
possible.</li>
<li>If there are many trades before ours, it may fail because the swapped price is
lower than <code>minPrice</code> (or the <code>slippage</code> is higher than the one set).</li>
</ul>
<p>The solution is to build an <a href="https://frontier.tech/a-tale-of-two-arbitrages">atomic arbitrage
bot</a> that takes the opportunity
opportunity in a single transaction and if it fails, the swaps are not executed.
Fortunately, as I wrote in the previous article, Starknet does not have these
problems.</p>
<h1 id="terminology">Terminology</h1>
<p>Before describing how the bot works, let&rsquo;s look at some definitions. If
you are already familiar with them skip this section.</p>
<ul>
<li>Token: a representation of an asset that has been tokenised, i.e. USDC or WBTC</li>
<li>Pair or trading pair: describes two tokens that are traded on an exchange,
i.e. BTC and USD</li>
<li>Base: the first token that appears in a pair</li>
<li>Quote: the second token that appears in a pair. Also known as counter currency</li>
<li>Symbol: a unique combination of letters and numbers that represents a pair
Usually, consists of the base and quote in the pair, e.g BTC/USD</li>
<li>Spread: a difference or gap between two prices</li>
<li>Bid: the highest price a buyer will pay to buy a token</li>
<li>Ask: the lowest price a seller will pay to sell a token</li>
</ul>
<h1 id="how-the-bot-works">How the bot works</h1>
<p>The bot consists of several steps:</p>
<ul>
<li>it fetches the tickers from both exchanges</li>
<li>it calculates the spread between the two tickers</li>
<li>if there is a profit, it tries to capture it by placing a buy/sell market
order on the exchanges.</li>
</ul>
<p>Here is a sequence diagram showing an interaction of the bot
<figure>
    <img
      src="/posts/starknet-arbitrage-basics/bot_sequence_diagram.png"
      alt="Sequence diagram showing the arbitrage bot fetching tickers from CEX and DEX, calculating spread, and placing orders"
      width="652"
      height="591"
      loading="lazy"
    /><figcaption>Sequence diagram of bot execution</figcaption></figure></p>
<h2 id="step-1-getting-data">Step 1: Getting data</h2>
<p>The bot needs to get the token prices from the exchange all the time. With
centralised exchanges, there is an endpoint called <em>ticker</em> that returns a
message like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;symbol&#34;</span>: <span style="color:#e6db74">&#34;BTCUSDT&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;priceChange&#34;</span>: <span style="color:#e6db74">&#34;-1067.94000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;priceChangePercent&#34;</span>: <span style="color:#e6db74">&#34;-1.619&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;weightedAvgPrice&#34;</span>: <span style="color:#e6db74">&#34;66057.25597271&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;prevClosePrice&#34;</span>: <span style="color:#e6db74">&#34;65957.82000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;lastPrice&#34;</span>: <span style="color:#e6db74">&#34;64889.88000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;lastQty&#34;</span>: <span style="color:#e6db74">&#34;0.00109000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;bidPrice&#34;</span>: <span style="color:#e6db74">&#34;64889.88000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;bidQty&#34;</span>: <span style="color:#e6db74">&#34;3.24493000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;askPrice&#34;</span>: <span style="color:#e6db74">&#34;64889.89000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;askQty&#34;</span>: <span style="color:#e6db74">&#34;0.16658000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;openPrice&#34;</span>: <span style="color:#e6db74">&#34;65957.82000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;highPrice&#34;</span>: <span style="color:#e6db74">&#34;66849.24000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;lowPrice&#34;</span>: <span style="color:#e6db74">&#34;64632.00000000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;volume&#34;</span>: <span style="color:#e6db74">&#34;20494.74928000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;quoteVolume&#34;</span>: <span style="color:#e6db74">&#34;1353826899.28544630&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;openTime&#34;</span>: <span style="color:#ae81ff">1722370827899</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;closeTime&#34;</span>: <span style="color:#ae81ff">1722457227899</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;firstId&#34;</span>: <span style="color:#ae81ff">3709477216</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;lastId&#34;</span>: <span style="color:#ae81ff">3711014970</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;count&#34;</span>: <span style="color:#ae81ff">1537755</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We are only interested in the price and quantity of the bid and ask (<code>bidPrice</code>,
<code>bidQty</code>, <code>askPrice</code>, <code>askQty</code> from the example).</p>
<p>AMMs work in a different way and they do not produce a ticker, so we need to
simulate it. There are two ways we can get the price from a pool:</p>
<ul>
<li>Ask the price directly from the pool (i.e. Uniswap V3 pools) or calculate
it from the liquidity (i.e. Uniswap V2 pools).</li>
<li>Simulate the trade with a specific amount and then calculate the price from
the amounts.</li>
</ul>
<p>The latter is better because it is more precise. In an AMM the amount you are
swapping affects the price. The higher the quantity, the more you unbalance the
pool (and the less you get), and the more the price changes. Thus, we can ask to
the pool to simulate a trade, calculate the price and then simulate the ticker:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>  initial_amount <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">10</span><span style="color:#f92672">**</span><span style="color:#ae81ff">18</span> <span style="color:#75715e"># amount that we are interested to swap</span>
</span></span><span style="display:flex;"><span>  amount_out <span style="color:#f92672">=</span> pool<span style="color:#f92672">.</span>swap(initial_amount) <span style="color:#75715e"># the pool returns the swapped amount</span>
</span></span><span style="display:flex;"><span>  price <span style="color:#f92672">=</span> amount_out <span style="color:#f92672">/</span> initial_amount
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># build a fake ticker message</span>
</span></span><span style="display:flex;"><span>  ticker <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;bidPrice&#34;</span>: price,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;bidAmont&#34;</span>: initial_amount,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;askPrice&#34;</span>: price,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;askAmont&#34;</span>: initial_amoun,
</span></span><span style="display:flex;"><span>  }
</span></span></code></pre></div><p>A fundamental concept to know is that <strong>prices are ALWAYS IN BASE</strong>. For this
reason, in the snippet, bid and ask are the same.</p>
<p>Once the tickers are fetched, we need to find the best bid and ask among them.
<strong>An important thing to understand</strong> is that we are trading against these
values. This mean that we will sell at the best bid and buy at the best ask.</p>
<p>If we only trade on two exchanges all we need to do is compare the two tickers
and get the best amounts. However, with multiple exchanges, the formula is more
general. Let $$N$$ be the number of exchanges we use, $$bid_i,\ ask_i$$ $$i \in
N$$ the bid and ask of exchange $$i$$. The $$bestBid$$ and $$bestAsk$$ are defined as</p>
\[
  bestBid = \max(bid_{1...N})
\]
\[
  bestAsk = \min(ask_{1...N})
\]
<h3 id="why-not-asking-the-order-book">Why not asking the order book?</h3>
<p>Instead of the ticker, the order book gives us much more information about how
much liquidity there is in the pair. This information can be useful in later
steps when the bot has to decide on the size of the orders.</p>
<p>The decision to use the ticker is because the logic around order sizing is
simple: given a predefined amount, the bot tries to create an order with that
amount. In addition, when a trade is made, the ticker is updated and, if the
opportunity is still there, we can continue with our swaps.</p>
<h2 id="step-2-calculate-the-spread">Step 2: Calculate the spread</h2>
<p>Once the bot has fetched best bid and ask, it can proceed to calculate the
spreads. As I wrote in <a href="#terminology">terminology</a>, the spread is the difference
between the bid/ask of one exchange and the ask/bid of the other. Let&rsquo;s say
$$A$$ and $$B$$ are two different exchanges, the spread is:</p>
\[
  spread = Bid_{A} - Ask_{B}
\]
\[
  spread\% = \frac{spread}{Bid_{A}}
\]
<p>Two spreads are calculated, one for each bid/ask side. If one of the spreads is
$$> 0$$ or greater than a threshold, the bot moves to the next step: sending the
orders.</p>
<h2 id="step-3-sending-orders">Step 3: Sending orders</h2>
<p>At this point we know there is an opportunity with and there is a profit to be
made. We smell 💵 💵 money 💵 💵. The bot needs to send the two orders to the
exchanges.</p>
<p>For the centralised exchange, we will send a <code>MARKET</code>. For the DEX, we will
build a transaction with the swap to do. As I mentioned in my <a href="https://lisot.to/posts/starknet-arbitrage-fundamentals/">previous
article</a>, in Starknet
transactions are executed immediately so we do not need to wait for the new
block to be mined.</p>
<p>But what is the order size? The strategy is very simple and consists of to trade
a pre-defined amount. However, the trade may not be successful for two reasons:</p>
<ul>
<li>Bid or ask amounts may be lower</li>
<li>Accounts or wallet may have less tokens</li>
</ul>
<p>We can solve the problem with the following formula. Let $$k$$ be the amount we
want to swap, $$ask$$ the price of ask, $$amount_{ask}$$ the maximum amount we
can trade at $$ask$$ price and $$wallet_{ask}$$ are the funds available on the
exchange. $$bid$$, $$amount_{bid}$$ and $$wallet_{bid}$$ are defined in the same
way. Therefore, the maximum value $$A$$ we can trade is:</p>
\[
A = \min(k,\ ask,\ bid,\ wallet_{bid},\ wallet_{ask} * ask)
\]
<p>Once the value to swap has been determined, the bot sends the orders, waits for
the response and starts again from the first step.</p>
<h1 id="project-setup">Project setup</h1>
<p>I chose Python to develop the bot. For this type of bot, a faster language like
Rust is not needed because the bottleneck is the latency of the exchanges.
Because Python is a high-level language, we can develop much faster, and you do
not have to worry about things like floating-point notation or big numbers.</p>
<p>As a dependency manager I use <a href="https://python-poetry.org/">poetry</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ poetry new stark-arbitrage
</span></span></code></pre></div><p>And install the dependencies</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ poetry add python-dotenv rich ccxt starknet-py
</span></span></code></pre></div><p>Note that <code>starknet-py</code> requires additional external packages. See
<a href="https://starknetpy.readthedocs.io/en/latest/installation.html">Installation</a>
for more information. The library is required to connect to Starknet and
retrieve account balances, and to sing and broadcast the transaction.</p>
<p>The centralised exchange that we will use is <a href="https://www.binance.com">Binance</a>
through <a href="https://github.com/ccxt/ccxt">ccxt</a>. For the decentralised exchange we
will use <a href="https://www.avnu.fi/">AVNU</a> to get the prices and to swap.</p>
<h2 id="ccxt">Ccxt</h2>
<p>Ccxt (CryptoCurrency eXchange Trading Library) is a cryptocurrency trading
library with support for many exchange markets, including Binance. The library
is multi-language (JS, Python, PHP and C#) and provides a unified API across
exchanges.</p>
<p>We are interested in the pro version because it offers support for websocket.</p>
<h2 id="avnu">AVNU</h2>
<p>AVNU is a decentralised exchange protocol (a dex aggregator) designed to provide
the best execution. The service has a nice API that returns the price and a
simulated price (in this case it provides the best route for our trade) for all
supported exchanges.</p>
<p>We will use AVNU to get the pool prices and ask it to create the transaction,
which we will then sign and broadcast.</p>
<h1 id="conclusions">Conclusions</h1>
<p>This article ends the long initial explanation on how Starknet and an arbitrage
bot works. In the next article I will start to explain how to develop the steps
described in the previous sections.</p>
<p><em>As I mentioned in my first article of the series there is a group to discuss
MEV on Starknet. If you are interested in the argument, want to discuss or have
some interesting information, you can find me there. <a href="https://t.me/+TiNIOKAdIyQzNDg0">Feel free to join the
group</a>.</em></p>
]]></description>
      
    </item>
    
    
    
    <item>
      <title>Building an Arbitrage Bot on Starknet: Part 0 - Understanding the Fundamentals</title>
      <link>https://lisot.to/posts/starknet-arbitrage-fundamentals/</link>
      <pubDate>Thu, 18 Jul 2024 12:11:57 +0200</pubDate>
      
      <guid>https://lisot.to/posts/starknet-arbitrage-fundamentals/</guid>
      <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>This article is part of a series on building an arbitrage bot between
centralised (CEX) and decentralised (DEX) exchanges on Starknet.</p>
<p>We&rsquo;ll explore Starknet&rsquo;s architecture, focusing on elements crucial for
arbitrage: the sequencer system and transaction lifecycle.</p>
<ul>
<li>Building an Arbitrage Bot on Starknet: Part 0 - Understanding the Fundamentals</li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-basics/">Building an Arbitrage Bot on Starknet: Part 1 - The basics</a></li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-bot/">Building an Arbitrage Bot on Starknet: Part 2 - The bot</a></li>
<li><a href="https://lisot.to/posts/starknet-arbitrage-conclusions/">Building an Arbitrage Bot on Starknet: Part 3 - Conclusions</a></li>
</ul>
<h1 id="what-is-starknet">What is Starknet</h1>
<p>Starknet is a permissionless Layer 2 solution for Ethereum that uses
zero-knowledge rollups (zk-rollups) to enhance scalability and security. Unlike
optimistic rollups, Starknet&rsquo;s approach allows for faster transaction finality
and enhanced security. Key features include:</p>
<ul>
<li>Cryptographic proofs for each block.</li>
<li><a href="https://www.starknet.io/blog/account-abstraction/">Account abstraction</a>.</li>
<li><a href="https://starkware.co/wp-content/uploads/2022/05/STARK-paper.pdf">STARK</a>
technology for proof generation.</li>
</ul>
<p>Here&rsquo;s how Starknet works:</p>
<ol>
<li>The chain produces a cryptographic proof for each block.</li>
<li>These proofs are then aggregated and published on Ethereum.</li>
<li>Anyone can take the proof and verify it independently.</li>
</ol>
<p>This process ensures transparency and security while leveraging the scalability
benefits of Layer 2 technology. Starknet consists of two specialised actors:
sequencers and provers. In this article, we will focus on the sequencers.</p>
<h1 id="the-sequencer">The sequencer</h1>
<p>Sequencers are the backbone of the Starknet network, similar to Ethereum&rsquo;s
validators. While validators ensure network security, sequencers in Starknet
provide transaction capacity. They serve as the access point for users to
interact with the chain, necessitating high reliability and availability.</p>
<p><figure>
    <img
      src="/posts/starknet-arbitrage-fundamentals/starknet-sequencer_hu6025233942982269635.png"
      alt="Diagram of the Starknet sequencer processing transactions through sequencing, executing, batching and block production stages"
      width="1200"
      height="500"
      loading="lazy"
    /><figcaption>Starknet sequencer</figcaption></figure></p>
<p>Sequencers play a crucial role in Starknet&rsquo;s operation, following a systematic
approach to transaction processing:</p>
<p>Sequencers follow this systematic method of processing transactions:</p>
<ol>
<li><strong>Sequencing</strong>: collecting transactions from users and ordering them.</li>
<li><strong>Executing</strong>: <a href="#transaction-lifecycle">processing the transactions</a></li>
<li><strong>Batching</strong>: executed transactions are grouped into batches</li>
<li><strong>Block production</strong>: batches are grouped into blocks</li>
</ol>
<p>Once sequencers complete these steps, provers take over to generate
cryptographic proofs of these blocks, which are then submitted to Ethereum for
final verification.</p>
<p>Although there are plans to decentralise Starknet
(<a href="https://community.starknet.io/t/starknet-decentralized-protocol-i-introduction/2671">here</a>
and
<a href="https://community.starknet.io/t/simple-decentralized-protocol-proposal/99693">here</a>),
the current centralised sequencer model provides us with two interesting
features: efficient transaction ordering and predictable transaction execution.</p>
<h2 id="first-come-first-serve-fcfs-model">First-Come-First-Serve (FCFS) model</h2>
<p>The sequencer operates on a <strong>First-Come-First-Serve</strong> (FCFS) basis, processing
transactions as they are received. This eliminates the need for mempool
monitoring and gas price competition, as the first transaction sent is the first
to be processed. In Starknet, it is all about being first.</p>
<p>Understanding the sequencer&rsquo;s role and the FCFS model is crucial for arbitrage
bot developers. The FCFS model shifts the focus from gas price optimization to
being the first to submit a transaction, fundamentally changing arbitrage
strategies on Starknet compared to other blockchain networks.</p>
<h2 id="block-production-limits">Block production limits</h2>
<p>Starknet implements block production limits to ensure network stability and
efficiency. Blocks are typically created every ~6 minutes or when specific
thresholds are met. For the most current information on these limits, refer to
the official <a href="https://docs.starknet.io/tools/limits-and-triggers/">Starknet Current Limits
guide</a>.</p>
<h1 id="transaction-lifecycle">Transaction lifecycle</h1>
<p>To better understand the execution of transactions within the sequencer, let&rsquo;s
examine the transaction lifecycle. There are three different types
of transactions:</p>
<ul>
<li><code>INVOKE</code> used for calling an existing function in a contract</li>
<li><code>DECLARE</code> used for declaring new contract classes and activating new contract
instances</li>
<li><code>DEPLOY_ACCOUNT</code> used for deploying new account contracts in smart wallets.</li>
</ul>
<p><figure>
    <img
      src="/posts/starknet-arbitrage-fundamentals/transaction-flow.png"
      alt="Flowchart of a Starknet transaction from submission through mempool validation, sequencer validation, execution and proof generation"
      width="972"
      height="432"
      loading="lazy"
    /><figcaption>Transaction flow in Starknet</figcaption></figure></p>
<p>At a high level, the Starknet transaction flow is as follows:</p>
<ol>
<li><strong>Transaction submission</strong>
<ul>
<li>User signs the transaction and sends it to a node.</li>
<li>The node broadcast the transaction</li>
<li>The transaction is marked as <code>RECEIVED</code> and added to the mempool</li>
</ul>
</li>
<li><strong>Mempool validation</strong>
<ul>
<li>Mempool performs a validation on the transaction (similar to the Ethereum&rsquo;s signature)</li>
<li><code>__validate__</code> function is called (or <code>__validate_declare__, __validate_deploy__</code> for respective transaction type) to ensure that the
transaction is valid</li>
<li>Invalid transaction are discarded</li>
</ul>
</li>
<li><strong>Sequencer validation</strong>
<ul>
<li>The sequencer performs a validation on the transaction before the execution
to ensure the transaction is still valid.</li>
<li>Invalid transaction are discarded</li>
</ul>
</li>
<li><strong>Execution</strong>:
<ul>
<li>Valid transactions are executed</li>
<li>Transactions are inserted into the pending block and state is updated</li>
<li>Transaction is marked as <code>ACCEPTED ON L2</code>. If a transaction fails during
execution, the status is set to <code>REVERTED</code>. Otherwise, the status is <code>SUCCESSED</code>.</li>
</ul>
</li>
<li><strong>Proof generation and verification</strong>: The prover computes the proof and
sends it to the L1 verifier.</li>
</ol>
<h1 id="combining-the-two-sequencer-and-transaction-lifecycle">Combining the two: sequencer and transaction lifecycle</h1>
<p>Why are these two aspects important to our arbitrage bot? While I was writing
this article explaining their importance, Starknet <a href="https://x.com/Starknet/status/1813514091069337634">made a
tweet</a> that answer the
question perfetcly.</p>
<p><figure>
    <img
      src="/posts/starknet-arbitrage-fundamentals/starknet-tweet_hu61981178382916355.png"
      alt="Tweet from Starknet announcing transaction finality in approximately 2 seconds"
      width="1200"
      height="1200"
      loading="lazy"
    /></figure></p>
<p>The answer is in &ldquo;transaction finality extremely fast&rdquo;. Although the tweet uses
the wrong term, in Starknet
<a href="https://ethereum.org/en/roadmap/single-slot-finality/#what-is-finality">finality</a>
occurs when the prover generates the block proof and publishes it on Ethereum.
Validity, on the other hand, is when the sequencer builds the block. The ~2
seconds can be seen as fast validity.</p>
<p>But how is this possible? How can we have &ldquo;finality&rdquo; in ~2 seconds when a block
is generated every ~6 minutes? The answer lies in stage 4 of the <a href="#transaction-lifecycle">transaction
lifecycle</a> or point 2 of the
<a href="#starknet-sequencer">sequencer</a>. <strong>After execution, the sequencer updates the
state of the chain.</strong> Because the sequencer is centralised and operate on a FCFS
basis, it can ensure that the transaction order is preserved, allowing the chain
state to be updated immediately. Block creation is a formality and only useful
for the provers.</p>
<p>This is very important for us because it means we can completely ignore the
mempool. <strong>We do not need to check it for pending swaps</strong> since the chain state
has already been updated. But Matteo, are you 100% sure? Let&rsquo;s follow the motto
&ldquo;Don&rsquo;t trust, verify&rdquo; and check if what I wrote is true.</p>
<p>I will illustrate this with two examples: first, the creation of an account, and
second, a simple swap on <a href="https://ekubo.org/">Ekubo</a>.</p>
<h2 id="account-creation">Account creation</h2>
<p>Once we have created the signer and generated the account we need to fund it for
deployment. The address of the account is
<code>0x067902aa3c1c65b35995d5bfe04f1fe949795f3510da71ba86bbf0e134402645</code>.</p>
<pre tabindex="0"><code>&gt; starkli balance 0x067902aa3c1c65b35995d5bfe04f1fe949795f3510da71ba86bbf0e134402645 --network sepolia
0.000000000000000000
</code></pre><p>After the faucet</p>
<pre tabindex="0"><code>&gt; starkli balance 0x067902aa3c1c65b35995d5bfe04f1fe949795f3510da71ba86bbf0e134402645 --network sepolia
0.025000000000000000
</code></pre><p>And deploy</p>
<pre tabindex="0"><code>&gt; starkli account deploy account3.json --keystore keystore3.json
The estimated account deployment fee is 0.000001863236340482 ETH. However, to avoid failure, fund at least:
    0.000002794854510723 ETH
to the following address:
    0x067902aa3c1c65b35995d5bfe04f1fe949795f3510da71ba86bbf0e134402645
Press [ENTER] once you&#39;ve funded the address.
Account deployment transaction: 0x06e1e2e16e28bdfe7d7faa6d49ebe080865e7b0b30e579322bf9aaf8656df4c3
Waiting for transaction 0x06e1e2e16e28bdfe7d7faa6d49ebe080865e7b0b30e579322bf9aaf8656df4c3 to confirm. If this process is interrupted, you will need to run `starkli account fetch` to update the account file.
Transaction not confirmed yet...
Transaction not confirmed yet...
Transaction 0x06e1e2e16e28bdfe7d7faa6d49ebe080865e7b0b30e579322bf9aaf8656df4c3 confirmed

&gt;starkli balance 0x067902aa3c1c65b35995d5bfe04f1fe949795f3510da71ba86bbf0e134402645 --network sepolia
0.024998136763659518
</code></pre><p>From Voyager you can see that the transactions have been executed, the chain
status has been updated and they are in the pending block.</p>
<p><figure>
    <img
      src="/posts/starknet-arbitrage-fundamentals/account-deploy_hu4341049791999203152.png"
      alt="Voyager block explorer showing the deployed account transaction in the pending block with updated chain state"
      width="1200"
      height="761"
      loading="lazy"
    /></figure></p>
<h2 id="swap">Swap</h2>
<p>For this example we swap STRK for ETH using <a href="https://ekubo.org/">Ekubo</a>. As you
can see before the swap we have <code>20.0 STRK</code> tokens.</p>
<p><figure>
    <img
      src="/posts/starknet-arbitrage-fundamentals/balance-before_hu6999511786534347367.png"
      alt="Wallet balance showing 20.0 STRK tokens before the swap"
      width="1200"
      height="174"
      loading="lazy"
    /></figure></p>
<p>Swap</p>
<p><figure>
    <img
      src="/posts/starknet-arbitrage-fundamentals/ekubo-swap.png"
      alt="Ekubo DEX interface showing a STRK to ETH swap transaction"
      width="1152"
      height="1038"
      loading="lazy"
    /></figure></p>
<p>Finally, the balance update</p>
<p><figure>
    <img
      src="/posts/starknet-arbitrage-fundamentals/balance-after_hu16859202858911626133.png"
      alt="Wallet balance updated after the swap, showing reduced STRK and increased ETH"
      width="1200"
      height="166"
      loading="lazy"
    /></figure></p>
<p><em>I found this group to discuss MEV on Starknet. If you are interested in the
argument, want to discuss or have some interesting information, you can find me
there. <a href="https://t.me/+TiNIOKAdIyQzNDg0">Feel free to join the group</a>.</em></p>
<h1 id="references">References</h1>
<ul>
<li>ZK-STARK: <a href="https://starkware.co/stark/">https://starkware.co/stark/</a> (July 2024)</li>
<li>Sequencers: <a href="https://docs.starknet.io/architecture-and-concepts/network-architecture/starknet-architecture-overview/#sequencers">https://docs.starknet.io/architecture-and-concepts/network-architecture/starknet-architecture-overview/#sequencers</a> (July 2024)</li>
<li>Transaction lifecycle: <a href="https://docs.starknet.io/architecture-and-concepts/network-architecture/transaction-life-cycle/">https://docs.starknet.io/architecture-and-concepts/network-architecture/transaction-life-cycle/</a> (July 2024)</li>
</ul>
]]></description>
      
    </item>
    
    
    
    <item>
      <title>About</title>
      <link>https://lisot.to/about/</link>
      <pubDate>Sat, 13 Jul 2024 19:32:59 +0200</pubDate>
      
      <guid>https://lisot.to/about/</guid>
      <description><![CDATA[<h2 id="hey-im-matteo-">Hey, I&rsquo;m Matteo! 👋</h2>
<p>I am a computer scientist currently working in the blockchain space and
interested in blockchain, AI and zero knowledge proofs.</p>
]]></description>
      
    </item>
    
    
    
    
    
    
  </channel>
</rss>
