<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: inge</title>
    <description>The latest articles on DEV Community by inge (@miguelvalenciav).</description>
    <link>https://dev.to/miguelvalenciav</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3840904%2Fa4265940-54ce-4707-92d8-503e7374df06.png</url>
      <title>DEV Community: inge</title>
      <link>https://dev.to/miguelvalenciav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miguelvalenciav"/>
    <language>en</language>
    <item>
      <title>How I Built a Dijkstra-Based Money Routing Engine with 29K Edges</title>
      <dc:creator>inge</dc:creator>
      <pubDate>Tue, 24 Mar 2026 00:44:19 +0000</pubDate>
      <link>https://dev.to/miguelvalenciav/how-i-built-a-dijkstra-based-money-routing-engine-with-29k-edges-1hcm</link>
      <guid>https://dev.to/miguelvalenciav/how-i-built-a-dijkstra-based-money-routing-engine-with-29k-edges-1hcm</guid>
      <description>&lt;h2&gt;
  
  
  The problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;A nurse in the Philippines sends $300 home every month. She's used the same service since 2012. She doesn't know that a two-step route through a stablecoin she's never heard of would save her $18 every month. Over twelve years, that's $2,500 — not stolen, just invisible.&lt;/p&gt;

&lt;p&gt;I wanted to build something that shows all the paths. Not a transfer service. Not a wallet. Just a map.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://coinnect.bot" rel="noopener noreferrer"&gt;Coinnect&lt;/a&gt; — an open-source routing engine that finds the cheapest way to move money between any two currencies using any combination of fiat, crypto, and P2P networks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling global money as a graph
&lt;/h2&gt;

&lt;p&gt;The core insight: every exchange, remittance provider, and P2P market is just an edge in a directed graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nodes = currencies (USD, MXN, BTC, USDT, NGN, GBP...)
Edges = conversion paths, each carrying:
  - exchange_rate
  - fee_pct
  - estimated_minutes
  - provider name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now the graph has &lt;strong&gt;29,000+ edges&lt;/strong&gt; across &lt;strong&gt;50+ currencies&lt;/strong&gt;, pulled from &lt;strong&gt;45+ live data sources&lt;/strong&gt; every 3 minutes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;21 crypto exchanges via CCXT (Binance, Kraken, Coinbase, OKX...)&lt;/li&gt;
&lt;li&gt;Wise live FX rates&lt;/li&gt;
&lt;li&gt;Regional exchanges with direct REST APIs (Bitso, Buda, VALR, CoinDCX, WazirX)&lt;/li&gt;
&lt;li&gt;Binance P2P real-time median prices for 12 emerging market currencies&lt;/li&gt;
&lt;li&gt;10 central bank official rates (Banxico, BCB, TRM, TCMB...)&lt;/li&gt;
&lt;li&gt;28 estimated remittance providers (Remitly, Western Union, Paysend, Nala...)&lt;/li&gt;
&lt;li&gt;Reference FX bridges for exotic corridors (CurrencyAPI, Frankfurter, FloatRates)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every edge gets refreshed in parallel via &lt;code&gt;asyncio.gather()&lt;/code&gt; in a background task. The whole refresh takes about 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The routing algorithm
&lt;/h2&gt;

&lt;p&gt;Finding the cheapest path from INR to GBP sounds simple until you realize the answer might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INR → USDT (CoinDCX, 0.5%)
    → BTC (MEXC, 0.1%)
    → GBP (Kraken, 0.16%)
    = 0.76% total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the "obvious" route — Wise at 2.8% — is almost 4x more expensive.&lt;/p&gt;

&lt;p&gt;The engine uses a modified Dijkstra with two passes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_dijkstra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;max_routes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])]&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;visited_states&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Prune by (currency, provider_set) — allows different
&lt;/span&gt;        &lt;span class="c1"&gt;# exchange combos through the same intermediate currency
&lt;/span&gt;        &lt;span class="n"&gt;providers_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;providers_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;visited_states&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;visited_states&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;visited_states&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Compound cost: 1 - ∏(1 - fee_i/100)
&lt;/span&gt;            &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fee_pct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;max_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
            &lt;span class="c1"&gt;# No provider appears twice in a path
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;new_amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr_amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exchange_rate&lt;/span&gt;
                          &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fee_pct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;new_priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fee_pct&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;optimize&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;estimated_minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;new_priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key design decisions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Compound cost, not additive.&lt;/strong&gt; If you chain three 1% fees, the real cost isn't 3% — it's &lt;code&gt;1 - (0.99 × 0.99 × 0.99) = 2.97%&lt;/code&gt;. Small difference, but it matters when comparing routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider deduplication.&lt;/strong&gt; No exchange appears twice in a path. This prevents circular nonsense like Binance → X → Binance and forces genuine diversity across providers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dual routing pass.&lt;/strong&gt; The quote endpoint runs Dijkstra twice: once on the full graph (including reference FX bridges for exotic corridors), once on real providers only (to find pure crypto multi-hop routes that would otherwise be overshadowed by cheaper reference data).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;K-shortest paths, not just shortest.&lt;/strong&gt; Classic Dijkstra prunes aggressively — once it visits BTC at step 1 via OKX, it blocks Kraken and Binance from reaching BTC at step 1. My state key includes the provider set, so different exchange combinations through the same currency all get explored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Max 4 hops.&lt;/strong&gt; Beyond 4 steps, the accumulated fees and complexity make routes impractical. The safety cap is 200K heap pops.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong (and fixed)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The 1% sanity check.&lt;/strong&gt; I had a filter: &lt;code&gt;they_receive &amp;gt; amount * 0.01&lt;/code&gt; to catch garbage routes. Worked great for USD→MXN. Completely broke INR→GBP — because 388 GBP is the correct answer for 50,000 INR, but 388 &amp;lt; 500 (the threshold calculated in INR units). Fix: just check &lt;code&gt;&amp;gt; 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Counter was counting pushes.&lt;/strong&gt; My safety cap of 50,000 iterations was counting heap pushes, not pops. With 29K edges, the counter hit 50K almost instantly without exploring anything useful. Switching to counting pops and raising the cap to 200K fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference providers in user-facing routes.&lt;/strong&gt; CoinGecko, central banks, and market rate bridges exist to help the router find exotic paths (like ZAR→ETB). But they're not real transfer services — you can't actually "send money via CoinGecko." I had to filter them from all user-facing results while keeping them available as intermediate hops.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;It's deliberately simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; with 4 uvicorn workers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; (WAL mode) for rate history and analytics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; built from source (34KB, not the 300KB CDN)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero JavaScript frameworks&lt;/strong&gt; — vanilla JS, template literals&lt;/li&gt;
&lt;li&gt;One Python process on one Linux server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Kubernetes. No microservices. No Redis. The whole thing runs on a single VPS and handles the current load without breaking a sweat.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The edges matter more than the algorithm.&lt;/strong&gt; Dijkstra is a solved problem. The hard part is getting accurate, fresh data from 45+ sources with different rate limits, auth methods, response formats, and failure modes. Wise blocks you if you make too many requests. CoinGecko rate-limits at 30 req/min. Binance P2P requires a POST with specific body format. Each adapter is its own little battle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reverse corridors are not obvious.&lt;/strong&gt; I had USD→INR but not INR→GBP. Someone searched for it, got "no routes found," and I realized we were missing 55 reverse corridors across 10 providers. Now we log every failed search to catch these gaps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;People don't compare.&lt;/strong&gt; The nurse in the Philippines isn't lazy. The information genuinely doesn't exist in one place. Most comparison sites are affiliate-funded and only show providers that pay them. The structural incentive is broken. That's why this has to be non-profit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it / break it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://coinnect.bot" rel="noopener noreferrer"&gt;coinnect.bot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API:&lt;/strong&gt; &lt;code&gt;GET https://coinnect.bot/v1/quote?from=USD&amp;amp;to=MXN&amp;amp;amount=500&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/coinnect-dev/coinnect" rel="noopener noreferrer"&gt;github.com/coinnect-dev/coinnect&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whitepaper:&lt;/strong&gt; &lt;a href="https://coinnect.bot/whitepaper" rel="noopener noreferrer"&gt;coinnect.bot/whitepaper&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built this alone over the past month. I'd genuinely appreciate feedback — especially on corridors that return wrong rates, providers I'm missing, or if you work in the remittance space and want to help. I'm looking for advisors and potentially a co-founder who knows this world better than I do.&lt;/p&gt;

&lt;p&gt;If you send money across borders, try your corridor. Tell me what's wrong. That's the most valuable thing you can do.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>cryptocurrency</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
