<?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>Forem: Erick Fernandez</title>
    <description>The latest articles on Forem by Erick Fernandez (@desmo).</description>
    <link>https://forem.com/desmo</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%2F1069148%2Fa841897c-305c-47a9-bfb3-dfeda7510af8.png</url>
      <title>Forem: Erick Fernandez</title>
      <link>https://forem.com/desmo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/desmo"/>
    <language>en</language>
    <item>
      <title>A Rust-Powered Starfield in the Browser with Macroquad + WASM</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Wed, 01 Apr 2026 18:22:05 +0000</pubDate>
      <link>https://forem.com/desmo/a-rust-powered-starfield-in-the-browser-with-macroquad-wasm-4hh3</link>
      <guid>https://forem.com/desmo/a-rust-powered-starfield-in-the-browser-with-macroquad-wasm-4hh3</guid>
      <description>&lt;p&gt;AI can get you to a working result quickly. What it doesn’t replace is the value of understanding why something looks right, breaks, or feels good to tune.&lt;/p&gt;

&lt;p&gt;I wanted a small Rust project where the mechanics stayed visible, and a browser-based starfield was a good fit: simple enough to build quickly, but visual enough to make the underlying math worth paying attention to.&lt;/p&gt;

&lt;p&gt;The nice part is that this effect is mostly a handful of small ideas wired together correctly. We are not building a full engine here. We are just using Rust, a rendering loop, and some basic projection math to create a convincing sense of forward motion.&lt;/p&gt;

&lt;p&gt;This walkthrough assumes you're comfortable reading basic Rust, but not necessarily familiar with Macroquad, WASM, or graphics code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Machine
&lt;/h2&gt;

&lt;p&gt;Before we get to the pixels, we need the right tools in the garage. For this project, we're using &lt;strong&gt;Macroquad's own web loader path&lt;/strong&gt;, which keeps the setup simple: compile to &lt;code&gt;wasm32-unknown-unknown&lt;/code&gt;, drop the output into a small &lt;code&gt;web/&lt;/code&gt; folder, and serve it like static files.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the Project
&lt;/h3&gt;

&lt;p&gt;If you do not already have Rust installed, start with &lt;code&gt;rustup&lt;/code&gt; from &lt;a href="https://rustup.rs/" rel="noopener noreferrer"&gt;rustup.rs&lt;/a&gt;. Once that is in place, create a new Rust binary project and move into the directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo new starfield &lt;span class="nt"&gt;--bin&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;starfield

&lt;span class="c"&gt;# Install the WASM target&lt;/span&gt;
rustup target add wasm32-unknown-unknown

&lt;span class="c"&gt;# Optional: install a simple static server for local testing&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;basic-http-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Blueprint (Cargo.toml)
&lt;/h3&gt;

&lt;p&gt;We only need a few parts to get this engine running. Macroquad handles the graphics, and we’ll use the &lt;code&gt;rand&lt;/code&gt; crate for our star distribution.&lt;/p&gt;

&lt;p&gt;I landed on this exact &lt;code&gt;rand&lt;/code&gt; config after a failed browser build. The &lt;code&gt;small_rng&lt;/code&gt; feature is enough for this project, and disabling &lt;code&gt;rand&lt;/code&gt;'s default features avoids pulling in browser glue that does not match Macroquad's plain HTML5 loader path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;macroquad&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.4"&lt;/span&gt;
&lt;span class="py"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;default-features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"small_rng"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The Glue (&lt;code&gt;web/index.html&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Macroquad's browser path expects a canvas and a JavaScript loader. Create a &lt;code&gt;web/&lt;/code&gt; directory, and inside it add an &lt;code&gt;index.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Rust Starfield&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;canvas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"glcanvas"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"mq_js_bundle.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;starfield.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note on &lt;code&gt;mq_js_bundle.js&lt;/code&gt;:&lt;/strong&gt; You need to place a copy of this loader in your &lt;code&gt;web/&lt;/code&gt; directory. You can usually find it in your local Cargo registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; ~/.cargo/registry/src/index.crates.io-&lt;span class="k"&gt;*&lt;/span&gt;/macroquad-&lt;span class="k"&gt;*&lt;/span&gt;/js/mq_js_bundle.js web/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Troubleshooting: If you cannot find it locally, you can download it directly from the &lt;a href="https://github.com/not-fl3/macroquad/blob/master/js/mq_js_bundle.js" rel="noopener noreferrer"&gt;Macroquad GitHub repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack: Macroquad &amp;amp; WASM
&lt;/h2&gt;

&lt;p&gt;There are powerful graphics options in Rust like &lt;strong&gt;Bevy&lt;/strong&gt;, but for this, I wanted something direct. &lt;/p&gt;

&lt;p&gt;I chose &lt;strong&gt;Macroquad&lt;/strong&gt;. It’s a minimalist engine with a low-friction API. It doesn't hide the execution loop behind much abstraction; you write a &lt;code&gt;loop&lt;/code&gt; and tell it exactly what to draw every frame. It feels less like building a factory and more like working directly on the engine block.&lt;/p&gt;

&lt;p&gt;When you compile to &lt;strong&gt;WASM&lt;/strong&gt;, you ship a self-contained binary. You still need that small JS shim (&lt;code&gt;mq_js_bundle.js&lt;/code&gt;) to bridge to WebGL, but the setup stays lean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of a Star
&lt;/h2&gt;

&lt;p&gt;The "star" is our basic unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Star&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;x, y:&lt;/strong&gt; Coordinates in space.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;z:&lt;/strong&gt; Depth. As &lt;code&gt;z&lt;/code&gt; decreases, the star moves closer.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;speed:&lt;/strong&gt; Allows for different layers of motion.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;brightness:&lt;/strong&gt; Adds visual depth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get moving, we populate a &lt;code&gt;Vec&lt;/code&gt; with randomized starting positions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;init_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Star&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SmallRng&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;seed_from_u64&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.map&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;Star&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;32.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.008&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;0.025&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two Rust details worth calling out here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;SmallRng::seed_from_u64(0)&lt;/code&gt; creates a deterministic field, which is great for debugging.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;(0..count).map(|_| ...)&lt;/code&gt; is the idiomatic way to say "repeat this &lt;code&gt;count&lt;/code&gt; times." The &lt;code&gt;|_|&lt;/code&gt; means we don't need the loop index.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;One small visual tweak:&lt;/strong&gt; If you keep &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; in too tight a range, stars bunch around the center. Widening the spawn volume (which we'll do in the "Polish" section) makes the field feel more believable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Infinite Loop (The Projection)
&lt;/h2&gt;

&lt;p&gt;The "warp" effect relies on a simple 3D-to-2D projection: as &lt;code&gt;z&lt;/code&gt; decreases, we scale coordinates to push stars further from the center.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="nf"&gt;.iter_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Move forward&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Recycle star&lt;/span&gt;
        &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;32.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Projection: dividing by 'z' is the key.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;screen_width&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;center_x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;screen_height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;center_y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;draw_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Full Engine (Core Version)
&lt;/h2&gt;

&lt;p&gt;Replace your &lt;code&gt;src/main.rs&lt;/code&gt; with this. It sets up the window, initializes the field, and runs the loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;macroquad&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rngs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SmallRng&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Rng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SeedableRng&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Star&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;32.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TITLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"RUST // WASM"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;init_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Star&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SmallRng&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;seed_from_u64&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map&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;Star&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.008&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;0.025&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[macroquad::main(&lt;/span&gt;&lt;span class="s"&gt;"Starfield"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;init_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SmallRng&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;seed_from_u64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;macroquad&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;miniquad&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;date&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;screen_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;screen_width&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;screen_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;screen_height&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;screen_w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;screen_h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nf"&gt;clear_background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="nf"&gt;.iter_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.speed&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;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;screen_w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;center_x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;screen_h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;center_y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;depth_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;depth_factor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.brightness&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;depth_factor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nf"&gt;draw_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;draw_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TITLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;next_frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening in &lt;code&gt;main()&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;screen_width()&lt;/code&gt; &amp;amp; &lt;code&gt;center_x&lt;/code&gt;: Recomputed every frame so it stays centered if you resize the browser.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;get_time()&lt;/code&gt;: Used for the pulsing title.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clear_background(...)&lt;/code&gt;: Every frame is drawn from scratch; we must clear the last one.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;0.5 + 0.5 * (time).sin()&lt;/code&gt;: Maps the sine wave (&lt;code&gt;-1.0..1.0&lt;/code&gt;) to a usable color range (&lt;code&gt;0.0..1.0&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running the Engine
&lt;/h2&gt;

&lt;p&gt;Build the WASM target (use &lt;code&gt;--release&lt;/code&gt; for performance):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo build &lt;span class="nt"&gt;--target&lt;/span&gt; wasm32-unknown-unknown &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;target/wasm32-unknown-unknown/release/starfield.wasm web/starfield.wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Serve the &lt;code&gt;web/&lt;/code&gt; folder (Python, Node, or &lt;code&gt;basic-http-server&lt;/code&gt; all work):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;basic-http-server web
&lt;span class="c"&gt;# OR: python3 -m http.server --directory web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automation (&lt;code&gt;build.sh&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;To make iteration faster, wrap this in a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4000
cargo build &lt;span class="nt"&gt;--target&lt;/span&gt; wasm32-unknown-unknown &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;target/wasm32-unknown-unknown/release/starfield.wasm web/starfield.wasm

&lt;span class="k"&gt;if &lt;/span&gt;lsof &lt;span class="nt"&gt;-i&lt;/span&gt; :&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Server running on &lt;/span&gt;&lt;span class="nv"&gt;$PORT&lt;/span&gt;&lt;span class="s2"&gt;. Refresh browser."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi
&lt;/span&gt;basic-http-server web &lt;span class="nt"&gt;-a&lt;/span&gt; 127.0.0.1:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Machine in Motion (The Polish)
&lt;/h2&gt;

&lt;p&gt;The "Core" version works, but it feels like a diagnostic test. Let's make it a finished visual.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tighten the Spawn Volume
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;const STAR_SPREAD: f32 = 20.0;&lt;/code&gt; at the top. Use this range in &lt;code&gt;init_stars&lt;/code&gt; and the recycling block:&lt;br&gt;
&lt;code&gt;star.x = rng.gen_range(-STAR_SPREAD..STAR_SPREAD);&lt;/code&gt;&lt;br&gt;
This makes the field feel deeper and less bunched.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Increase Density and Speed
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;main()&lt;/code&gt;, bump the count to &lt;code&gt;650&lt;/code&gt; and the speed range in &lt;code&gt;init_stars&lt;/code&gt; to &lt;code&gt;0.012..0.032&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Center the Title
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;measure_text&lt;/code&gt; to center the title perfectly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;84.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;measure_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TITLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_size&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;center_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;title_metrics&lt;/span&gt;&lt;span class="py"&gt;.width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;center_y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;title_metrics&lt;/span&gt;&lt;span class="py"&gt;.height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;draw_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TITLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Optimized Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/desmodrone/rust-wasm-starfield" rel="noopener noreferrer"&gt;rust-wasm-repo&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rngs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SmallRng&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Rng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SeedableRng&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;macroquad&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// One star in 3D space, plus a few values for motion and brightness.&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Star&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;32.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TITLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"RUST // WASM"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;init_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Star&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fixed seed so the initial field is repeatable while developing.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SmallRng&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;seed_from_u64&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.map&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;Star&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Spread stars across a wider 3D volume so the field fills the screen.&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.012&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;0.032&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[macroquad::main(&lt;/span&gt;&lt;span class="s"&gt;"Starfield"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create the initial field once at startup.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;init_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;650&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Time-based seed so recycled stars re-enter less predictably.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SmallRng&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;seed_from_u64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;macroquad&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;miniquad&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;date&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Recompute dimensions every frame so the effect stays centered.&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;screen_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;screen_width&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;screen_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;screen_height&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;screen_w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;screen_h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Paint the background before drawing the stars on top.&lt;/span&gt;
        &lt;span class="nf"&gt;clear_background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nf"&gt;draw_rectangle&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="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;screen_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;screen_h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="nf"&gt;.iter_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 1. Move the star forward along the Z-axis&lt;/span&gt;
            &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.speed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// 2. Recycle the star if it gets too close to the "windshield"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="c1"&gt;// Respawn across the full width of the field, not just the center.&lt;/span&gt;
                &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;STAR_SPREAD&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// 3. Project to 2D screen: dividing by 'z' is the key.&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;screen_w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;center_x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;screen_h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;center_y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// 4. Scale size and brightness based on depth&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;depth_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;STAR_DEPTH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;depth_factor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;star&lt;/span&gt;&lt;span class="py"&gt;.brightness&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;depth_factor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Draw brighter, larger stars as they get closer to the camera.&lt;/span&gt;
            &lt;span class="nf"&gt;draw_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Smoothly oscillate between 0.0 and 1.0 for the title pulse&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;84.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;measure_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TITLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_size&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;center_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;title_metrics&lt;/span&gt;&lt;span class="py"&gt;.width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;title_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;center_y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;title_metrics&lt;/span&gt;&lt;span class="py"&gt;.height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;draw_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TITLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title_color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Present the current frame, then continue the loop&lt;/span&gt;
        &lt;span class="nf"&gt;next_frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We’ve built the universe; now we just need a way to pay for the fuel.&lt;/p&gt;

&lt;p&gt;The obvious next step is to make the effect more physical: star trails, camera drift, or mouse input. But even in this stripped-down form, the core lesson holds up: if you want a visual Rust project that feels rewarding quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://macroquad.rs/" rel="noopener noreferrer"&gt;Macroquad Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://rustwasm.github.io/docs/book/" rel="noopener noreferrer"&gt;Rust WASM Book&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gamedev</category>
      <category>rust</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Start the year securely with these development checklists</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Wed, 14 Jan 2026 15:27:38 +0000</pubDate>
      <link>https://forem.com/extropy/start-the-year-securely-with-these-development-checklists-3e9e</link>
      <guid>https://forem.com/extropy/start-the-year-securely-with-these-development-checklists-3e9e</guid>
      <description>&lt;h2&gt;
  
  
  Start the year securely with these development checklists
&lt;/h2&gt;

&lt;p&gt;The exploit landscape is forever changing, to help you ensure you make a safe start to 2026 we are releasing some security checklists for you to use when developing in Web3. The first looks at architecture and procedures in general. &lt;br&gt;
Join our &lt;a href="https://discord.gg/kpTY3xFby6" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt; and let us know if you find these useful, and above all stay safe in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web3 Development General Security Checklist
&lt;/h2&gt;

&lt;p&gt;This checklist is derived from the critical findings and recommendations from our 2025 End-of-Year Security Review.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Defensive Architecture &amp;amp; Fail-Safe Engineering
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-Chain Invariant Enforcement:&lt;/strong&gt; Have you implemented automated checks that revert state transitions if fundamental economic properties (e.g., total supply, collateral ratios, or reward balances) are violated?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Algorithmic Circuit Breakers:&lt;/strong&gt; Are there automated pause mechanisms triggered by abnormal volatility, suspicious outflow patterns, or internal state desynchronisation?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formal Verification for Logic Safety:&lt;/strong&gt; Have you transitioned from simple unit testing to property-based testing and formal verification to prove protocol maths remains sound under extreme edge-case conditions?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implicit vs. Explicit Failure:&lt;/strong&gt; Is the system architected to degrade gracefully or halt entirely when core invariants are threatened, rather than assuming ideal behaviour?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade Safety&lt;/strong&gt; Is there an upgrade path that has been tested. Can an attacker force an upgrade ? Are upgrades monitored ? &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Precision Access Control &amp;amp; Authority Management
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scoped Capability Management:&lt;/strong&gt; Have you moved away from monolithic admin roles toward granular, time-locked capabilities?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Duties:&lt;/strong&gt; Are critical tasks and privileges distributed among distinct individuals to prevent single points of failure?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whitelisted Governance Execution:&lt;/strong&gt; Is all governance-led interaction restricted to a pre-approved registry of contracts and function signatures, eliminating arbitrary low-level calls?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational Security (OpSec) Hygiene:&lt;/strong&gt; Is "Least Privilege" access enforced for dev-ops pipelines and deployment environments?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Identity Integrity &amp;amp; Cross-Chain Security
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain Separation &amp;amp; Replay Protection:&lt;/strong&gt; Do all off-chain messages mandate EIP-712 (or equivalent) domain separation, cryptographically binding signatures to a specific Chain ID, contract address, and unique user nonce?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolver-Contract Synchronisation:&lt;/strong&gt; For hybrid or ZK-powered systems, is the off-chain resolver state a perfect mirror of the on-chain source of truth?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic Handshakes:&lt;/strong&gt; Do backends require a cryptographically verified handshake from the smart contract before updating local state?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Identity Verification:&lt;/strong&gt; Does the code clearly distinguish between a Signer (initiator) and an Object Owner (asset holder) to eliminate "Confused Deputy" vulnerabilities?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. User Protection &amp;amp; Behavioural Security
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simulation-First UX:&lt;/strong&gt; Does the user interface integrate transaction simulation to provide "clear-signing" transparency, preventing blind-signing of malicious requests?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adversarial UX Testing:&lt;/strong&gt; Has the user interaction flow been stress-tested against phishing, wallet compromise, and transaction misdirection?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident Response Readiness:&lt;/strong&gt; Is there a documented and tested recovery plan for compromised keys, including pre-deployed emergency pause contracts?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>Extropy Security Bytes: w1 &amp; w2, 2026</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Tue, 13 Jan 2026 10:50:00 +0000</pubDate>
      <link>https://forem.com/extropy/extropy-security-bytes-w1-w2-2026-1mdb</link>
      <guid>https://forem.com/extropy/extropy-security-bytes-w1-w2-2026-1mdb</guid>
      <description>&lt;p&gt;Welcome back to &lt;strong&gt;Extropy Security Bytes&lt;/strong&gt;, where we discuss the latest incidents shaping the Web3 landscape.&lt;/p&gt;

&lt;p&gt;Throughout 2025, we dissected the industry’s most critical incidents together. Now, as we present our first roundup of 2026, the new year has already delivered a wave of critical headlines. Attackers take no breaks — and neither can we.&lt;/p&gt;




&lt;h2&gt;
  
  
  Truebit: The $26 Million Zombie Code
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“When legacy math meets modern liquidity, the result is always catastrophe.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On January 8, the Truebit Protocol suffered the first major exploit of 2026, losing roughly &lt;strong&gt;$26 million (8,535 ETH)&lt;/strong&gt; to a vulnerability that should have stayed in the past. The attack wasn’t a complex new vector; it was a classic integer overflow in a legacy smart contract that had seemingly been forgotten. The attacker exploited this unmaintained code to mint millions of TRU tokens at near-zero cost, bypassing supply caps effortlessly because the contract pre-dated modern Solidity’s native overflow checks.&lt;/p&gt;

&lt;p&gt;Once the infinite mint was complete, the attacker dumped the tokens back into the protocol, draining all available liquidity and causing the TRU token price to collapse by nearly 100% in 24 hours. The impact was total: a project’s market cap evaporated overnight because a single legacy file was left live and unmonitored. While the team scrambled to coordinate with law enforcement, the on-chain damage was already irreversible.&lt;/p&gt;

&lt;p&gt;In a pattern that is becoming standard operating procedure, the attacker immediately funneled the 8,535 ETH into Tornado Cash, obliterating the trail before any freeze could be attempted. Security firms have since linked the wallet to a previous exploit of the Sparkle Protocol, suggesting this was the work of a sophisticated, repeat offender who specifically hunts for abandoned, “zombie” contracts that teams have neglected to deprecate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legacy is Liability:&lt;/strong&gt; Old contracts do not age like wine; they age like milk. If a contract is live, it must be monitored or deprecated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overflows are Still Real:&lt;/strong&gt; Just because modern Solidity (0.8.0+) handles overflows doesn’t mean your legacy stack does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The “Zombie” Risk:&lt;/strong&gt; Nearly 80% of projects hit by such exploits never recover their full value. Abandoned code is a loaded gun waiting for a trigger.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Aave: The Governance Civil War
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“When freedom from regulators becomes a license to fight over who owns the treasury.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Aave spent four years fighting the SEC to prove decentralized finance could work. On December 16, 2025, they won that battle, only to immediately start a civil war over the spoils. The conflict centered on a &lt;strong&gt;$10 million annual feestream&lt;/strong&gt; from the Aave frontend that was flowing to Aave Labs (Stani Kulechov’s company) instead of the DAO treasury. When former CTO Ernesto Boado proposed that the DAO should legally own all brand assets to correct this, the response was a textbook case of governance capture.&lt;/p&gt;

&lt;p&gt;On December 21, Aave Labs escalated Boado’s proposal to a Snapshot vote without his consent, triggering a voting period that ran directly through Christmas. This “Grinch” move forced the community to mobilize during the holidays, causing the AAVE token to drop 25% as markets priced in the chaos. The proposal’s own author urged users to abstain, delegitimizing the entire process. The vote ultimately failed with 55% opposing and 41% abstaining, but the damage to trust was profound.&lt;/p&gt;

&lt;p&gt;Now, the protocol enters “Phase 2” of negotiations. Stani Kulechov has offered revenue sharing, while delegate leader Marc Zeller is demanding full asset ownership transfer with enforceable “guardrails” for stewardship. The protocol itself works perfectly — holding $35 billion in TVL — but the governance layer is fractured. When a founder can force a vote without the author’s consent to protect a revenue stream, “decentralization” becomes a polite fiction, and the governance token becomes a proxy for political risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Governance is an Attack Vector:&lt;/strong&gt; Internal power struggles can damage token value as effectively as a hack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing Matters:&lt;/strong&gt; Forcing votes during holidays destroys legitimacy and trust; it signals an attempt to bypass scrutiny.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership vs. Stewardship:&lt;/strong&gt; DAOs must clearly define who owns the brand versus who runs the website before the revenue becomes significant.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TMXTribe: The 36-Hour Bleed
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Was this incompetence so profound it became indistinguishable from malice?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Between January 5 and January 7, TMXTribe (a GMX fork on Arbitrum) watched &lt;strong&gt;$1.4 million&lt;/strong&gt; drain from their protocol over a continuous 36-hour window. The exploit was mechanically simple: a loop that minted LP tokens, swapped them for the internal stablecoin (USDG), and unstaked, repeated ad infinitum. Because the contracts were unverified, the exact flaw remains opaque to the public, but the result was a systematic drainage of the treasury.&lt;/p&gt;

&lt;p&gt;What makes this incident damning is the team’s behavior. While the protocol bled, the team was active on-chain, deploying new contracts and executing upgrades throughout the attack window. Yet, despite being present and capable of signing transactions, they never triggered an emergency pause. They sent an on-chain bounty message to the attacker asking for a return of funds, but failed to take the one technical action that would have stopped the theft.&lt;/p&gt;

&lt;p&gt;The attacker ignored the bounty, bridged the funds to Ethereum via Across Protocol, and washed them through Tornado Cash. Meanwhile, the team remained silent on Twitter for days, leaving users to watch the drain in real-time. When a team is competent enough to upgrade contracts during a hack but “forgets” to hit the pause button, the line between negligence and complicity blurs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unverified Contracts are Red Flags:&lt;/strong&gt; TMXTribe ran unverified code. You cannot audit what you cannot see, and you cannot trust a team that hides their logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 36-Hour Test:&lt;/strong&gt; If a protocol cannot stop a slow drain in 36 hours, it effectively has no security operations center (SOC).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pause Functionality is Mandatory:&lt;/strong&gt; A kill switch is the most basic defense; failing to use it while actively online is inexcusable.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Ledger: The Supply Chain Leak
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Your keys are safe, but your home address isn’t.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On January 5, Ledger confirmed a data breach impacting its customers — not through their hardware, but through their payment processor, Global-e. While Ledger’s devices remain secure and user funds are untouched, the personal data of customers — including names, shipping addresses, and contact information — was compromised. This breach exposes users to a threat that cryptography cannot solve: the physical “wrench attack.”&lt;/p&gt;

&lt;p&gt;The breach occurred when Global-e identified “unusual activity” that exposed customer databases. Attackers now possess a list of individuals who own crypto hardware wallets, along with their physical locations. This creates a high-fidelity target list for phishing campaigns (fake Ledger letters, emails) and potentially home invasions. It is a bitter irony for a company that previously faced backlash for charging for security features; now, their “free” supply chain partners have cost users their privacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vendor Risk is Your Risk:&lt;/strong&gt; You can have the best hardware security in the world, but if your payment processor leaks the customer list, your users are in danger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect Phishing:&lt;/strong&gt; Victims should expect sophisticated phishing attempts using the stolen data to establish false trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physical OpSec:&lt;/strong&gt; Delivering hardware wallets to your home address creates a permanent record connecting your identity to crypto ownership. Use drop boxes or aliases where possible.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  MetaMask: The “Party Hat” Phishing Campaign
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“The drainer doesn’t need your seed phrase; it just needs your permission.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A sophisticated phishing campaign flagged by ZachXBT has drained over &lt;strong&gt;$107,000&lt;/strong&gt; from hundreds of wallets by exploiting the holiday lull. Users received professionally designed emails from “MetaLiveChain” claiming a “Mandatory 2026 Upgrade” was required to keep their wallets active. The emails used legitimate marketing templates (complete with unsubscribe links from real vendors) and featured a disarming “party hat” version of the MetaMask fox logo to bypass user suspicion.&lt;/p&gt;

&lt;p&gt;Instead of asking for a seed phrase — which most users now know to protect — the phishing site prompted users to sign a contract approval. This granted the attacker permission to move unlimited tokens from the victim’s wallet. By keeping individual thefts small (typically under $2,000), the attacker avoided triggering major alerts while scaling the theft across hundreds of victims. It serves as a reminder that a signature can be just as lethal as a leaked key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Approvals are Dangerous:&lt;/strong&gt; You can lose your funds without ever sharing your seed phrase. Never sign a transaction from an email link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small Drains Add Up:&lt;/strong&gt; Attackers are moving towards “low and slow” thefts to stay under the radar of major security firms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the Sender:&lt;/strong&gt; MetaMask will never email you about a “mandatory upgrade.” Any email claiming otherwise is a scam.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Futureswap: The Unverified Black Box
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“If the code is closed, the exit is open.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Security firm BlockSec Phalcon detected a quiet drain on the Futureswap protocol on Arbitrum, resulting in an estimated loss of &lt;strong&gt;$395,000&lt;/strong&gt;. The attack appears to be caused by an accounting error in the &lt;code&gt;stableBalance&lt;/code&gt; calculation during position updates, allowing the attacker to withdraw more USDC than they were entitled to.&lt;/p&gt;

&lt;p&gt;However, confirming the root cause is impossible because the contracts are unverified. Closed-source contracts prevent security researchers from analyzing the flaw or helping the team patch it. As of now, the team has not responded to inquiries, and users are left in the dark about whether their remaining funds are safe. This incident reinforces the golden rule of DeFi: if the code isn’t verified on the block explorer, you aren’t using a decentralized protocol — you’re trusting a black box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verify or Don’t Trust:&lt;/strong&gt; Unverified contracts are a massive security risk. They hide bugs from whitehats and hide exploits from users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silence is Telling:&lt;/strong&gt; A lack of communication during an exploit typically signals a lack of capability to respond.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  GlassWorm: The macOS Developer Trap
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“The malware isn’t targeting your wallet; it’s targeting your codebase.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A fourth wave of the GlassWorm malware campaign has been detected, specifically targeting macOS developers through malicious VSCode and OpenVSX extensions. Unlike previous waves that targeted Windows, this iteration uses AppleScript and LaunchAgents to establish persistence on Mac systems. The malware hides inside seemingly useful developer tools (like &lt;code&gt;studio-velte-distributor&lt;/code&gt;) and executes an AES-encrypted payload after a 15-minute delay to evade sandbox analysis.&lt;/p&gt;

&lt;p&gt;The malware’s goal is total credential theft. It scrapes GitHub, NPM, and OpenVSX credentials, giving attackers access to the victim’s code repositories and supply chain. More alarmingly, it contains code designed to replace legitimate hardware wallet apps (like Ledger Live and Trezor Suite) with trojanized versions. While this specific feature appears to be misconfigured in the current version (deploying empty files), the intent is clear: attackers are trying to compromise the developer’s entire environment, from their code to their cold storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit Your Extensions:&lt;/strong&gt; VSCode extensions run with high privileges. Only install extensions from verified publishers with established reputations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Supply Chain is the Target:&lt;/strong&gt; Attackers want your GitHub credentials to inject malware into your projects, infecting your users downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delayed Execution:&lt;/strong&gt; Modern malware waits before striking. A clean scan immediately after download does not guarantee safety.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About Extropy
&lt;/h2&gt;

&lt;p&gt;Since 2017, Extropy has been at the forefront of blockchain security, auditing smart contracts across Ethereum and Zero-Knowledge (ZK) protocols. We have collaborated with leading ecosystems, including Base, Starknet, and MINA, ensuring their smart contracts are resilient, efficient, and secure.&lt;/p&gt;

&lt;p&gt;We specialise in DeFi, on-chain games, and ZK applications, leveraging formal verification, static analysis, and deep manual reviews to uncover vulnerabilities before they become exploits. Whether you’re working with Solidity, Rust, Cairo, or zkVMs, our collaborative approach ensures your project meets the highest security standards.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; security.extropy.io
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:info@extropy.io"&gt;info@extropy.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ethereum</category>
      <category>blockchain</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Into the Ocean</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Wed, 07 Jan 2026 14:24:51 +0000</pubDate>
      <link>https://forem.com/extropy/into-the-ocean-30b3</link>
      <guid>https://forem.com/extropy/into-the-ocean-30b3</guid>
      <description>&lt;p&gt;We are leaving the &lt;a href="https://moonmaths.xyz/chapter-2.html" rel="noopener noreferrer"&gt;Bay of Rainbows&lt;/a&gt;. behind us the "Golden Handle" that perfect polynomial curve is fading.&lt;br&gt;
We are heading South-West, down into the largest basin on the moon: Oceanus Procellarum, The Ocean of Storms.&lt;/p&gt;

&lt;p&gt;Why? Because we have a problem.&lt;br&gt;
The smooth curves of &lt;a href="https://moonmaths.xyz/chapter-2.html" rel="noopener noreferrer"&gt;Chapter 2&lt;/a&gt; are too "heavy" for us. They rely on real numbers : decimals that stretch to infinity.&lt;br&gt;
But our computers are finite, in the digital world, we cannot handle infinity, and we cannot afford the inaccuracy of rounding down.&lt;/p&gt;

&lt;p&gt;Fortunately, we are on the Moon.&lt;/p&gt;

&lt;p&gt;If you travel West across the Ocean of Storms and keep going, you don't vanish into infinity. Eventually you wrap around the sphere that is the Moon and return to where you started. The surface of the Moon is a closed loop.&lt;/p&gt;

&lt;p&gt;In Chapter 3 we will apply this "closed loop" logic to our mathematics. We will leave the rocky terrain of standard integers where division often fails.&lt;br&gt;
We are moving to a Finite Field.&lt;/p&gt;

&lt;p&gt;A Field is a mathematical landscape where you can add, subtract, multiply and divide freely without ever falling off the map.&lt;br&gt;
It is a closed system that allows us to have the complexity of curves with the absolute precision of integers.&lt;/p&gt;

&lt;p&gt;We are heading to the Ocean of Storms because we need an ocean to freely sail on, a place where math works in every direction.&lt;/p&gt;

&lt;p&gt;But be warned, when you force a smooth curve into a closed loop it shatters. The Golden Handle is about to turn into stardust.&lt;/p&gt;

</description>
      <category>math</category>
      <category>blockchain</category>
      <category>zeroknowledge</category>
      <category>web3</category>
    </item>
    <item>
      <title>The $3 Billion Loss Year: End-of-Year Security Report</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Wed, 31 Dec 2025 12:12:57 +0000</pubDate>
      <link>https://forem.com/extropy/the-3-billion-loss-year-end-of-year-security-report-1c7g</link>
      <guid>https://forem.com/extropy/the-3-billion-loss-year-end-of-year-security-report-1c7g</guid>
      <description>&lt;p&gt;2025 made it unmistakably clear: in Web3, security is no longer a background concern or merely a checkbox; it is the defining factor separating the projects that survived from the ones that collapsed. With an estimation of more than &lt;strong&gt;$3 billion&lt;/strong&gt; in losses across hacks, scams, protocol failures, and key management breaches this year, the ecosystem was forced into a harsh confrontation with its own maturity. While innovation accelerated, attacker sophistication and manoeuvring attacks outpaced both, revealing fundamental weaknesses that are no longer optional to address.&lt;/p&gt;

&lt;p&gt;In Extropy, as a security auditing firm working across L1s, L2s, DeFi, gaming, and ZK applications, we reviewed real codebases and attack surfaces that could have resulted in catastrophic losses. We offer a picture of where Web3 security stands today and what development teams must prioritise going into 2026.&lt;/p&gt;




&lt;h2&gt;
  
  
  Some of the issues our audits uncovered this year
&lt;/h2&gt;

&lt;p&gt;Our audits throughout the year exposed a pattern: even well-funded teams with experienced developers repeatedly fell into the same high-impact traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Incorrect Reward Accounting in Move Modules
&lt;/h3&gt;

&lt;p&gt;One of the most consequential findings occurred within a Move-based staking system due to a subtle accounting error in the reward distribution logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Issue:&lt;/strong&gt; The function incorrectly subtracted the &lt;code&gt;collectable_reward&lt;/code&gt; from the total removed value without isolating the principal from the reward flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Snippet:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Move&lt;/span&gt;
&lt;span class="c1"&gt;// native_pool.move (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;collectable_reward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reward_amounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;total_removed_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total_removed_value&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;collectable_reward&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Incorrect deduction&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; This failure to treat principal and rewards as distinct streams leads to corrupted validator balances, systemic reward imbalances (insolvency), and arithmetic cascades during subsequent cycles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. total_supply Underflow Causing Global DoS (Move)
&lt;/h3&gt;

&lt;p&gt;We identified a high-severity vulnerability in a ticket-based staking protocol where critical global state variables were manipulated before validation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Issue:&lt;/strong&gt; The &lt;code&gt;total_supply&lt;/code&gt; was modified in the local execution context before verifying the user's available balance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; If the &lt;code&gt;ticket_amount&lt;/code&gt; exceeds the balance, the transaction correctly reverts, but only after the &lt;code&gt;total_supply&lt;/code&gt; has been modified. In specific runtime environments, this can lead to an underflow that freezes the contract's core functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Settlement-Phase Financial Manipulation (Solidity)
&lt;/h3&gt;

&lt;p&gt;During a Solidity audit, we uncovered a dangerous settlement design where external interactions and internal state updates were intermingled.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Issue:&lt;/strong&gt; Relying solely on the &lt;code&gt;onlyAdmin&lt;/code&gt; modifier created a "security-by-access-control" fallacy that ignored the risks of compromised or malicious admin keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Snippet:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Solidity&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;settle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;external&lt;/span&gt; &lt;span class="nx"&gt;onlyAdmin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;uint256&lt;/span&gt; &lt;span class="nx"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculatePayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;userBalances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Updated before final validation&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bool&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Mixed with external call&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; This allowed for state inconsistency and atomic misordering, demonstrating that security must be enforced at the state level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Resolver / State Desynchronisation in ZK Games
&lt;/h3&gt;

&lt;p&gt;In ZK-powered gaming systems (Mina/o1js), we identified a recurring structural weakness involving desynchronisation between off-chain resolvers and on-chain verification.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Issue:&lt;/strong&gt; The backend infrastructure updated game states independently of the zkApp's on-chain validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; Without a cryptographic "handshake," the system was vulnerable to forged state transitions and replay inconsistencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Lack of Replay Protection &amp;amp; Weak Message Identity
&lt;/h3&gt;

&lt;p&gt;A pervasive issue across multi-chain and intent-based systems was the absence of robust message-identity controls.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Issue:&lt;/strong&gt; Hashing functions lacked domain separators or nonces, making signatures vulnerable to collision or forgery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; Signatures authorized for an action on one chain could be replayed to drain funds on another, a primary driver of cross-chain liquidity loss in 2025.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Analysis: The State of Web3 Security
&lt;/h2&gt;

&lt;p&gt;These findings reveal a fundamental shift in the threat landscape where attackers have pivoted toward architectural exploitation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Failure of "Safe-by-Default":&lt;/strong&gt; Over-reliance on the safety of Move and Rust led to "psychological safety," where developers assumed compilation precluded the need for invariant enforcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification Gap:&lt;/strong&gt; In ZK systems, the weakest link is often the off-chain resolver—the bridge that must maintain consistency between the proof, the contract, and the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational &amp;amp; Admin Risk:&lt;/strong&gt; Privileged admin pathways were responsible for approximately &lt;strong&gt;$1.6 billion (70%)&lt;/strong&gt; of stolen funds in the first half of 2025.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-Centric Vectors:&lt;/strong&gt; Roughly &lt;strong&gt;40% of losses&lt;/strong&gt; occurred via phishing, unauthorised permit signatures, and front-running, indicating that operational workflows remain highly vulnerable.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Recommendations for 2026
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Defensive Architecture:&lt;/strong&gt; Implement automated on-chain invariant checks and algorithmic circuit breakers to degrade gracefully during attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precision Access Control:&lt;/strong&gt; Move toward granular, time-locked capabilities and enforce "Least Privilege" for dev-ops pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity Integrity:&lt;/strong&gt; Mandate EIP-712 domain separation and ensure off-chain resolvers are cryptographically bound to the on-chain source of truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Protection:&lt;/strong&gt; Integrate transaction simulation into UIs to prevent "blind-signing" and conduct adversarial UX testing against phishing.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Future Outlook
&lt;/h3&gt;

&lt;p&gt;In 2026, we expect security to serve as a core business differentiator. As highlighted at Abu Dhabi Fintech Week 2025, institutional adoption hinges on provable resilience. The threat landscape will continue to professionalise with AI-driven social engineering and autonomous predator swarms, reinforcing that Web3 security is now full-stack system security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;The future of Web3 security is not about the total elimination of risk, but about the rigorous containment and engineering of that risk. &lt;br&gt;
The tools exist; the remaining question is whether development teams will treat security as an indispensable foundation or an afterthought.&lt;br&gt;
Over the next few weeks will be supplying security guidelines and checklists for Web3 developers to ensure that your start to 2026 is a secure one.&lt;/p&gt;

&lt;p&gt;Originally published on: &lt;a href="https://security.extropy.io/articles/eoy-security-report-2025" rel="noopener noreferrer"&gt;security.extropy.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>security</category>
      <category>zeroknowledge</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>The Moon and the Maths: The Sea of Tranquility</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Tue, 30 Dec 2025 15:57:38 +0000</pubDate>
      <link>https://forem.com/extropy/the-moon-and-the-maths-the-sea-of-tranquility-1c9g</link>
      <guid>https://forem.com/extropy/the-moon-and-the-maths-the-sea-of-tranquility-1c9g</guid>
      <description>&lt;h2&gt;
  
  
  Chapter 1 - The Sea of Tranquility
&lt;/h2&gt;

&lt;p&gt;This is an exploration of the Moon and the 'Moon Maths' that underlies Zero Knowledge Proofs. &lt;/p&gt;

&lt;p&gt;Starting at the Apollo 11 landing site our journey will take us around the moon, each chapter introducing a new area of Maths. &lt;br&gt;
The journey is based on the outline of the Moon Math Manual, a fantastic resource for learning ZKP Maths.&lt;/p&gt;

&lt;h3&gt;
  
  
  What will we take on our journey?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The Moon math manual (&lt;a href="https://github.com/LeastAuthority/moonmath-manual/releases/latest/download/main-moonmath.pdf" rel="noopener noreferrer"&gt;download link&lt;/a&gt;) this is the guide to the maths we will be discussing, and we can try some of its exercises as we travel.&lt;/li&gt;
&lt;li&gt;Some music to help us think, an appropriate choice would be &lt;a href="https://en.wikipedia.org/wiki/Apollo:_Atmospheres_and_Soundtracks" rel="noopener noreferrer"&gt;Apollo Atmospheres and Soundtracks by Brian Eno.&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Charts to help us navigate, fortunately NASA has made many charts &lt;a href="https://www.lpi.usra.edu/resources/mapcatalog/" rel="noopener noreferrer"&gt;available&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;For companions we have Peggy and Victor : &lt;br&gt;
Peggy will take the role of the person creating proofs, with Victor the verifier of the proofs.&lt;br&gt;&lt;br&gt;
Victor is a sceptic and always concerned that Peggy maybe trying to cheat him and provide an incorrect proof. &lt;br&gt;
Peggy is most concerned about privacy, she wants to limit the information she provides to Victor.&lt;/p&gt;

&lt;p&gt;We are ready to set off, but where are we going ?  &lt;/p&gt;

&lt;p&gt;Our journey takes us from the well understood landscape of the Sea of Tranquility to the mysterious dark side of the moon, from basic mathematics to elliptic curves and the processes used in ZKP systems&lt;/p&gt;

&lt;p&gt;Our itinerary  :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 1:&lt;/strong&gt; Sea of Tranquility (The basics).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 2:&lt;/strong&gt; Bay of Rainbows (Polynomials).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 3:&lt;/strong&gt; Ocean of Storms (Finite Fields).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 4:&lt;/strong&gt; Apennine Mountains (Abstract Algebra).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 5:&lt;/strong&gt; The Far Side (Elliptic Curves).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 6:&lt;/strong&gt; Sea of Moscow (R1CS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 7:&lt;/strong&gt; Crater Tycho (Trusted Setup).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chapter 8 :&lt;/strong&gt; The North Pole (Groth16  and True Zero Knowledge).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A conflict in the Sea of Tranquility
&lt;/h3&gt;

&lt;p&gt;There is a conflict between &lt;strong&gt;Verification&lt;/strong&gt; and &lt;strong&gt;Privacy&lt;/strong&gt;.&lt;br&gt;
Imagine you want to prove you are 18, you could hand over a driving licence or passport, revealing your name, address, and exact birth date. &lt;br&gt;
You give away &lt;em&gt;everything&lt;/em&gt; to prove &lt;em&gt;one thing&lt;/em&gt;.&lt;br&gt;
In the digital world most blockchains choose transparency, great for verifiability but no privacy&lt;br&gt;&lt;br&gt;
Banks emphasise privacy, your details remain private to the outside world, but transactions are not verifiable, we have to trust the bank.&lt;/p&gt;

&lt;p&gt;The solution to the conflict lies with Zero Knowledge Proofs (ZKP) , we can have both verifiability (mathematical proofs) and privacy, the person verifying the proof learns no new information.&lt;/p&gt;

&lt;p&gt;There are many analogies to help visualise ZKPs , lets try some : &lt;/p&gt;

&lt;p&gt;Imagine that our journey to the moon started out as a secret search for an alien artifact (as happened in 2001 : A Space Odyssey) &lt;br&gt;
As a research student you think you have discovered the site of the artifact in a photograph, and you need to share this with your supervisor, but if you give the exact coordinates, he will claim the discovery for himself. &lt;br&gt;
A way round this would be to cover the photograph with a large piece of cardboard with just an artifact shaped hole in the cardboard. Your supervisor would be able to see the artifact through the hole, but not gain a frame of reference from other landmarks and so not learn the location.&lt;br&gt;
They know you found it, but they have zero knowledge of &lt;em&gt;where&lt;/em&gt; it is.&lt;/p&gt;

&lt;p&gt;Of course physically covering information is no use for digital assets, we need mathematical constructs to allow us to hide information.&lt;/p&gt;

&lt;p&gt;Our journey then is an exploration of the mathematical concepts we need to create and verify zero knowledge proofs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Leaving the Sea of Tranquility
&lt;/h2&gt;

&lt;p&gt;The aim in this first chapter is to establish the background in a well known setting, in the Sea of Tranquility (Mare Tranquillitatis). &lt;br&gt;
Our destination is the &lt;strong&gt;Bay of Rainbows&lt;/strong&gt;, some 1,500 kilometres away. &lt;br&gt;
Initially we will head north to take us to the the Sea of Serenity (Mare Serenitatis) .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs30rbq2exbzyiem7lwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs30rbq2exbzyiem7lwt.png" alt="CH1a" width="800" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To the right:  the sun is rising in the East.&lt;br&gt;
Behind us the Earth hangs permanently in the sky at about 60 degrees elevation. It never moves.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lunar surface
&lt;/h3&gt;

&lt;p&gt;The ground we are travelling over is grey and flat. The surface is called 'regolith' it is a collection of shattered rock and charcoal grey powder  that covers the bedrock. It has been formed by meteorite impact, but the particles are sharp and can easily damage equipment. .&lt;/p&gt;

&lt;p&gt;The horizon is deceptively close only a few kilometres away curving sharply because the moon is small. &lt;br&gt;
There is no atmosphere to scatter the light, so there is no 'distance' haze. A rock 100 metres away looks as sharp and black as a rock 10 kilometres away. It is easy to lose your sense of scale and perspective. &lt;/p&gt;

&lt;p&gt;Similarly in the realm of proofs it is easy to lose perspective, we need to establish some rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ground rules for ZKPs
&lt;/h2&gt;

&lt;p&gt;Analogies are fine, but we need to clarify more rigorously what our requirements are for a ZKP. If we were to build a ZKP system it would have to follow 3 rules&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Completeness  :&lt;/strong&gt; If Peggy is honest and the statement she is proving is true, the math always works. Victor will always accept her proof&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Soundness :&lt;/strong&gt; If the statement is false, Peggy cannot cheat. The probability of fooling Victor is negligible. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;'Zero Knowledgeiness'  :&lt;/strong&gt; If the statement is true, Victor learns nothing else. He is unable to reverse engineer the proof to find any of its secrets.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One hurdle many have encountered when studying ZKPs is the terminology involved, making sense of unfamiliar symbols and terms slows us down, acts as a drag on our understanding. &lt;br&gt;
So while we are on familiar ground, lets go through some symbols we will be using, starting with the classification of numbers.&lt;/p&gt;

&lt;p&gt;If you cast your mind back to school we progressed from using numbers that felt familiar and were used for familiar task such as counting, on to fractions, and then stranger numbers such as pi. Overtime we would have become more rigorous in our classification of these numbers. &lt;br&gt;
Fortunately on this journey we stay with the simplest types of numbers. &lt;/p&gt;

&lt;h3&gt;
  
  
  Integers
&lt;/h3&gt;

&lt;p&gt;These are whole numbers, we represent them with the symbol $\mathbb{Z}$ &lt;/p&gt;

&lt;h3&gt;
  
  
  Rational Numbers
&lt;/h3&gt;

&lt;p&gt;These are numbers that can be represented as a ratio of integers for these we use the symbol $\mathbb{Q}$&lt;/p&gt;

&lt;h3&gt;
  
  
  Integers , division and remainders
&lt;/h3&gt;

&lt;p&gt;Imagine we have decided to only work with Integers.&lt;br&gt;
If we divide two integers we may get an integer as a result, for example 12 divided by 4 gives 3, but if we divide 13 by 4 we don't get an integer, but we could say we get 4 with a remainder of 1. &lt;br&gt;
In ZKP maths we are often just concerned with remainders, like when we are telling the time we may only be interested in the minute hand rather than the hour hand, here the minutes represent a remainder. &lt;br&gt;
Doing arithmetic in this way is called &lt;strong&gt;modular&lt;/strong&gt; arithmetic, often called Clock Maths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Congruence
&lt;/h3&gt;

&lt;p&gt;If we explore the remainders we see a concept called congruence, this is grouping together integers that produce the same remainder under division. &lt;/p&gt;

&lt;p&gt;For example &lt;/p&gt;

&lt;p&gt;13 divided by 4 gives remainder 1&lt;br&gt;
17 divided by 4 gives remainder 1&lt;br&gt;
21 divided by 4 gives remainder 1&lt;br&gt;
and so on &lt;/p&gt;

&lt;p&gt;The numbers 13, 17 and 21 ... are said to be congruent.&lt;/p&gt;

&lt;p&gt;We will investigate more arithmetic in chapter 3 when we reach the Ocean of Storms and look at fields. &lt;/p&gt;




&lt;p&gt;We leave the Sea of Tranquility, rising up to our left is the  &lt;strong&gt;Apennine Mountain Range&lt;/strong&gt;. &lt;br&gt;
These are 5,000 metre peaks higher than the Alps forming a massive wall.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Crater Plinius&lt;/strong&gt; stands sentinel here. It is a sharp, 43km-wide crater with a central peak. &lt;/p&gt;

&lt;p&gt;We are leaving the familiar behind and heading for the unknown, time to introduce more terminology and concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Computational Asymmetry &amp;amp; The Witness
&lt;/h3&gt;

&lt;p&gt;As you read about ZKPs you will come across the term 'witness', this is what we call the secret data, the information that Peggy knows and wants to prove she knows, but doesn't want to reveal to Victor. &lt;br&gt;
The statement is the claim that Peggy is making, she is claiming that she knows the witness, it is this claim that is to be proven.&lt;/p&gt;

&lt;p&gt;A fundamental concept in cryptography is Asymmetry, we shall return to this many times in the journey.&lt;br&gt;
In computation there is a difference between &lt;em&gt;doing&lt;/em&gt; the work and &lt;em&gt;checking&lt;/em&gt; the work. &lt;br&gt;
For example&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finding the Alien Artifact (The Witness):&lt;/strong&gt; This is hard. It requires a vehicle, fuel, time, and luck. It is computationally expensive. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Checking the Photograph (The Statement):&lt;/strong&gt; This is easy. It takes a split second.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If solving a problem were easy, we wouldn't need a proof—we would just solve it ourselves. &lt;br&gt;
 ZKPs rely on this imbalance, indeed this is an aspect of ZKPs that can seem too good to be true.&lt;br&gt;
 We can prove we have done the "hard work" (found the Witness) without forcing the Verifier to redo the whole journey.&lt;/p&gt;

&lt;p&gt;This comes into play when we design ZKP systems, we often say that the system needs to be 'succinct' , that is the proof should be small enough to be manageable, and the time taken to verify it should be reasonably short. &lt;br&gt;
Later in our journey I will be much more rigorous about these definitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FSJnZpIZNbe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FSJnZpIZNbe.png" alt="CH1&amp;amp;2" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Crossing into the Sea of Rains (Mare Imbrium)
&lt;/h3&gt;

&lt;p&gt;We cannot drive over the Apennine mountains. We have to pass through the &lt;strong&gt;Hadley-Apennine Gap&lt;/strong&gt; (this is where Apollo 15 landed). &lt;/p&gt;

&lt;p&gt;This is a narrow corridor where the mountains meet the marshy &lt;strong&gt;Palus Putredinis&lt;/strong&gt; (Marsh of Decay).&lt;/p&gt;

&lt;p&gt;To our immediate left, a massive winding canyon called &lt;strong&gt;Hadley Rille&lt;/strong&gt;, to the right, the towering Mount Hadley.&lt;/p&gt;

&lt;p&gt;We won't climb the Apennines yet that is a challenge for Chapter 4, when we tackle the heavy lifting of Abstract Algebra.&lt;/p&gt;

&lt;p&gt;Once we clear the mountains, the world opens up. We enter the Sea of Rains. &lt;br&gt;
This is a gigantic, flat impact basin. &lt;br&gt;
It is an ocean of stone.&lt;br&gt;
We have 1,000km of open driving here.&lt;/p&gt;

&lt;p&gt;We navigate by three massive craters in the distance: &lt;strong&gt;Archimedes&lt;/strong&gt;, &lt;strong&gt;Autolycus&lt;/strong&gt;, and &lt;strong&gt;Aristillus&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crossing the Terminator
&lt;/h2&gt;

&lt;p&gt;As we travel northwest we will come to the terminator line, marking the boundary between day and night. &lt;br&gt;
On Earth light is reflected from the atmosphere giving a period of dusk. &lt;br&gt;
The lack of atmosphere on the moon means that the change from day to night is much more abrupt. &lt;/p&gt;

&lt;p&gt;Such a sharp distinction between light and dark is something we want for our proofs. we want an exact and easily tested cutoff between a valid and an invalid proof.&lt;/p&gt;

&lt;p&gt;In the underlying maths, we achieve this through probability. While theoretically 'probabilistic', the chance of a false proof being accepted is so vanishingly small (like guessing a specific grain of sand in this desert) that for all practical purposes, it is a sharp, solid wall. &lt;br&gt;
Victor can be as confident in the math as he is that the Earth will never set&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking ahead
&lt;/h2&gt;

&lt;p&gt;The shadows are getting longer. The math is about to get steeper.&lt;br&gt;
Next stop: &lt;strong&gt;Sinus Iridum&lt;/strong&gt; where we will learn that every secret is just a point on a polynomial curve.&lt;/p&gt;




&lt;p&gt;For more resources about ZKPs and Maths visit our &lt;a href="https://academy.extropy.io" rel="noopener noreferrer"&gt;Academy&lt;/a&gt;&lt;br&gt;
This content will always be free, if you would like to support our work, you can donate &lt;br&gt;
to the following addresses : &lt;br&gt;
Bitcoin : bc1q528tct2mgn7zl99reuaj0ag770asgeurce5kkl&lt;br&gt;
Ethereum : 0xcc1E7bdA08Abdcf164E9D2E4f78aEDb1d811593D&lt;/p&gt;

</description>
      <category>zeroknowledge</category>
      <category>math</category>
      <category>blockchain</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>The Quantum Event Horizon: Cryptographic Vulnerabilities in the Ethereum Network</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Mon, 29 Dec 2025 15:38:50 +0000</pubDate>
      <link>https://forem.com/extropy/the-quantum-event-horizon-cryptographic-vulnerabilities-in-the-ethereum-network-23a9</link>
      <guid>https://forem.com/extropy/the-quantum-event-horizon-cryptographic-vulnerabilities-in-the-ethereum-network-23a9</guid>
      <description>&lt;p&gt;The intersection of quantum computing and blockchain presents a substantial cryptographic challenge. As Ethereum secures hundreds of billions in digital assets, its reliance on classical Elliptic Curve Cryptography (ECC) constitutes a quantifiable risk. &lt;/p&gt;

&lt;p&gt;There has recently been much debate, often acrimonious, about this risk.&lt;br&gt;
In this series of three articles I will provide the technical background and examine the threats to Ethereum, Bitcoin and the process of transitioning to a quantum safe blockchain.&lt;/p&gt;

&lt;p&gt;In this first article I want to explore the threat that, what are known as Cryptographically Relevant Quantum Computers (CRQCs), pose to the Ethereum ecosystem.&lt;br&gt;
Even if we can transition to Post-Quantum Cryptography (PQC) we will face economic as well as purely cryptographic challenges. &lt;br&gt;
The primary candidate solution, Module-Lattice-Based Digital Signature Algorithm (ML-DSA), entails signature sizes 40 to 60 times larger than current standards. &lt;br&gt;
To move to this would have severe consequences for gas costs and network throughput. Successful migration depends on Account Abstraction (ERC-4337) and the maturation of Layer 2 scaling solutions to manage the associated data overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cryptographic Debt of the Decentralised Economy
&lt;/h2&gt;

&lt;p&gt;Ethereum relies on the computational hardness of the discrete logarithm problem to secure Externally Owned Accounts (EOAs) via the secp256k1 curve and in the consensus mechanism via the BLS12-381 curve. &lt;br&gt;
These primitives were selected for efficiency and compact signature sizes. however, the emergence of quantum computing invalidates the assumption that deriving a private key from a public key is computationally infeasible.&lt;/p&gt;

&lt;p&gt;Unlike classical computers, quantum algorithms can solve the discrete logarithm problem in polynomial time, if you are unfamiliar with this term, you can think of this as being a feasible amount of time.&lt;br&gt;
For a permissionless blockchain, this point is critical; if the underlying cryptography is compromised, then the ledger loses integrity. &lt;/p&gt;

&lt;h2&gt;
  
  
  Quantum Mechanics and Cryptographic Vulnerability
&lt;/h2&gt;

&lt;p&gt;Lets have a look at what is possible in quantum computation, the threat to cryptography is affected by algorithmic efficiency and hardware stability, specifically the distinction between physical and logical qubits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quantum algorithms : Shor’s algorithm and Grover's algorithm
&lt;/h3&gt;

&lt;p&gt;Shor’s algorithm provides an exponential speedup in solving the hidden subgroup problem. While classical algorithms require exponential time to solve the Elliptic Curve Discrete Logarithm Problem (ECDLP), Shor’s algorithm reduces this to polynomial time. &lt;br&gt;
What this means for blockchains is that a CRQC could effectively break the ECDSA scheme,  authorising transactions by deriving a private key from a public key rapidly.&lt;br&gt;
Another quantum algorithm Grover’s algorithm offers only a quadratic speedup for hashing algorithms like Keccak-256. &lt;br&gt;
This reduces 256-bit security to 128-bit security, which remains is still seen as robust robust. &lt;br&gt;
Consequently, the immediate failure point is the signing algorithm, not the hash function.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 523 Logical Qubit Threshold
&lt;/h3&gt;

&lt;p&gt;A critical metric for forecasting the threat is the number of logical qubits (error-corrected units) required to break secp256k1. Recent research suggests an algorithmic lower bound of 523 logical qubits. Even conservative models estimating 2,500 logical qubits present a concerning timeline when compared against hardware roadmaps projecting systems with over 1,000 logical qubits by the early 2030s.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Intercept Problem
&lt;/h3&gt;

&lt;p&gt;The urgency arises from the disparity between hardware velocity and migration latency. Quantum development is accelerating, while upgrading a decentralised protocol requires social consensus, standardisation, and user migration. If hardware capabilities outpace the migration timeline, Ethereum faces an "intercept" scenario where the network is vulnerable before defences are fully deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ethereum’s Unique Attack Surface
&lt;/h2&gt;

&lt;p&gt;Ethereum’s architecture creates specific vulnerabilities across the execution and consensus layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution Layer: Externally Owned Accounts (EOAs)
&lt;/h3&gt;

&lt;p&gt;An Ethereum address is a hash of the public key. Addresses that have never sent a transaction are quantum-safe because the public key remains unrevealed. However, once a transaction is signed and broadcast, the public key is recorded on the blockchain. A quantum attacker could harvest exposed public keys from the chain's history and derive the corresponding private keys from those.&lt;/p&gt;

&lt;p&gt;This specifically threatens high-value targets such as protocol treasuries and early adopters who reuse addresses. &lt;br&gt;
Unlike the UTXO model which encourages address rotation, Ethereum's account-based model encourages identity persistence, increasing the attack surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  The risk to the consensus layer: BLS Signatures
&lt;/h3&gt;

&lt;p&gt;The Proof-of-Stake mechanism utilises BLS signatures for their aggregation properties, allowing thousands of validator attestations to be verified simultaneously. A quantum breach of the BLS scheme would allow attackers to forge attestations, equivocate without penalty, and compromise finality.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mempool: Quantum Front-Running
&lt;/h3&gt;

&lt;p&gt;Migration itself presents a risk. When a user broadcasts a transaction to move funds to a quantum-secure wallet, the public key is revealed in the mempool. A quantum adversary could derive the private key and broadcast a competing transaction with a higher gas fee, effectively stealing the funds before the migration transaction is processed. We will explore this risk further in the final article in the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Post-Quantum Cryptographic Alternatives
&lt;/h2&gt;

&lt;p&gt;To mitigate these risks, Ethereum must transition to PQC standards finalised by NIST in 2024, here are some candidate solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lattice-Based Cryptography: ML-DSA
&lt;/h3&gt;

&lt;p&gt;The primary candidate is ML-DSA (formerly Dilithium). It relies on the 'Module Learning With Errors' problem. While verification is computationally efficient, the data requirements are significant. &lt;br&gt;
An ML-DSA signature is approximately 3,309 bytes, compared to 65 bytes for ECDSA. This represents a substantial increase in data availability costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hash-Based Cryptography: SLH-DSA
&lt;/h3&gt;

&lt;p&gt;SLH-DSA (formerly SPHINCS+) serves as a conservative backup relying solely on hash functions. However, its signature sizes (8KB to 30KB) and slow verification speeds make it impractical for standard transaction throughput.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero-Knowledge Proofs (STARKs)
&lt;/h3&gt;

&lt;p&gt;STARKs offer a blockchain native alternative. They allow users to generate a proof of knowledge of a private key. Their primary advantage is recursive aggregation, where thousands of proofs can be compressed into a single proof, potentially resolving the scaling issues inherent in other PQC solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparative Analysis of these solutions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ECDSA (Current)&lt;/th&gt;
&lt;th&gt;ML-DSA&lt;/th&gt;
&lt;th&gt;SLH-DSA&lt;/th&gt;
&lt;th&gt;STARK Proofs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discrete Logarithm&lt;/td&gt;
&lt;td&gt;Module Lattices&lt;/td&gt;
&lt;td&gt;Hash Functions&lt;/td&gt;
&lt;td&gt;Hash Functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quantum Resistance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broken&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Very Strong&lt;/td&gt;
&lt;td&gt;Very Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Public Key Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;33 bytes&lt;/td&gt;
&lt;td&gt;1,952 bytes&lt;/td&gt;
&lt;td&gt;32-64 bytes&lt;/td&gt;
&lt;td&gt;N/A (Hashed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Signature Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;65 bytes&lt;/td&gt;
&lt;td&gt;3,309 bytes&lt;/td&gt;
&lt;td&gt;~8KB - 30KB&lt;/td&gt;
&lt;td&gt;Tiny (Aggregated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gas Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Prohibitive&lt;/td&gt;
&lt;td&gt;High Fixed Cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Economics and Feasibility of Transition
&lt;/h2&gt;

&lt;p&gt;The "defensive downgrade" we face highlights that PQC migration increases costs without immediate functional benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gas and Throughput
&lt;/h3&gt;

&lt;p&gt;Ethereum’s block gas limit constrains computational throughput. The 50-fold increase in signature size required by ML-DSA would drastically raise the base cost of transactions due to calldata pricing. &lt;br&gt;
Without optimisation, this could reduce Layer 1 throughput by 70-80%.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Bloat and Centralisation
&lt;/h3&gt;

&lt;p&gt;Moving to larger public keys increases the size of the Ethereum state. This necessitates higher hardware requirements for node operators, potentially driving centralisation. &lt;br&gt;
Solutions such as Stateless Clients or Verkle Trees (alreaady part of the Ethereum roadmap) are essential prerequisites to mitigate this bloat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account Abstraction
&lt;/h3&gt;

&lt;p&gt;Account Abstraction (ERC-4337) is critical for migration. It decouples the asset-holding address from the signature scheme, allowing Smart Contract Wallets to upgrade their validation logic. &lt;br&gt;
With this we couls have a smooth transition where wallets can rotate from ECDSA to ML-DSA or STARKs without moving funds to a new address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emergency Response: The Quantum Hard Fork
&lt;/h2&gt;

&lt;p&gt;In the event of a sudden quantum breakthrough, a "Freeze and Recover" strategy has been proposed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Rollback:&lt;/strong&gt; The chain reverts to a state prior to the attack.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Freeze:&lt;/strong&gt; The protocol rejects all EOA transactions.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Recovery:&lt;/strong&gt; Users prove ownership via ZK-proofs of their seed phrase (which remains quantum-secure as it is hashed).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach carries significant governance risks. The coordination required to execute a hard fork takes time, during which markets could suffer catastrophic disruption. Furthermore, the political decision of when to rollback could undermine trust in the network's immutability. Recall the controversy regarding the DAO hardfork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The vulnerability of secp256k1 to a quantum computer with approximately 523 logical qubits places the risk horizon potentially within the early 2030s. &lt;br&gt;
While emergency protocols exist, they would entail severe economic disruption. &lt;br&gt;
The sustainable path involves proactive adoption of Account Abstraction and ML-DSA signatures. &lt;br&gt;
This transition will fundamentally alter network economics, necessitating reliance on Layer 2 scaling to absorb the data overhead required for post-quantum security.&lt;/p&gt;

</description>
      <category>quantum</category>
      <category>ethereum</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>Moon Math Itinerary: The Lunar Map of ZK</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Fri, 19 Dec 2025 15:33:13 +0000</pubDate>
      <link>https://forem.com/extropy/moon-math-itinerary-the-lunar-map-of-zk-3pb2</link>
      <guid>https://forem.com/extropy/moon-math-itinerary-the-lunar-map-of-zk-3pb2</guid>
      <description>&lt;p&gt;They call it "Moon Math" because it feels alien.&lt;/p&gt;

&lt;p&gt;They call it "Zero Knowledge" because it feels impossible.&lt;/p&gt;

&lt;p&gt;I’m writing a new series to demystify the foundational math of ZKPs. But instead of dry textbooks, we’re mapping the maths to a journey around the Moon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Itinerary:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Sea of Tranquility:&lt;/strong&gt; The Paradox of Privacy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Bay of Rainbows:&lt;/strong&gt; Turning Code into Curves (Polynomials).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Ocean of Storms:&lt;/strong&gt; The endless sea of Finite Fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The Far Side:&lt;/strong&gt; The dark, complex world of Elliptic Curves.&lt;/p&gt;

&lt;p&gt;...and 4 more stops ending at the Peaks of Eternal Light.&lt;/p&gt;

&lt;p&gt;From the safety of the Near Side to the complex geometry of the Far Side—we’re going to map it all.&lt;/p&gt;

&lt;p&gt;Our first stage takes us from the Sea of Tranquility to the Bay of Rainbows, from bottom right to top left on the chart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00cqbotw7i97v1r9rlnk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00cqbotw7i97v1r9rlnk.png" alt="Lunar map of ZK" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>zeroknowledgeproofs</category>
      <category>zkp</category>
      <category>math</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Why Zero Knowledge Proofs are like a Lunar Landscape.</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Mon, 15 Dec 2025 13:30:50 +0000</pubDate>
      <link>https://forem.com/extropy/why-zero-knowledge-proofs-are-like-a-lunar-landscape-o7b</link>
      <guid>https://forem.com/extropy/why-zero-knowledge-proofs-are-like-a-lunar-landscape-o7b</guid>
      <description>&lt;p&gt;We're writing a series exploring the famous "Moon Math" paper, but we are taking the name literally. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are going on an 8-stop journey around the Moon to learn ZKP&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;My favourite stop?  &lt;strong&gt;Chapter 2: The Bay of Rainbows (Sinus Iridum).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In ZKPs, the hardest mental shift is understanding how we turn a rigid computer program into a geometric shape. &lt;br&gt;
It’s exactly like the landscape of Sinus Iridum.&lt;/p&gt;

&lt;p&gt;We take scattered "rocks" of data—disconnected computational steps and use interpolation to smooth them into a continuous, perfect arc, just like the mountain range bordering the bay.&lt;/p&gt;

&lt;p&gt;If you move just one rock (falsify one step of the code), the entire curve ripples and changes shape. &lt;/p&gt;

&lt;p&gt;That "fragility" is how we prove truth without revealing secrets.&lt;/p&gt;

&lt;p&gt;Join us to explore the beauty of the Moon and the beauty of Maths&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmkvqxk8w25ehlv8eycc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmkvqxk8w25ehlv8eycc.jpg" alt="the moon, bay of rainbows" width="593" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>web3</category>
      <category>zeroknowledge</category>
      <category>math</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>An Analysis of Arbitrage Markets Across Ethereum, Solana, Optimism, and Starknet (2024-2025)</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Fri, 12 Dec 2025 17:57:01 +0000</pubDate>
      <link>https://forem.com/extropy/an-analysis-of-arbitrage-markets-across-ethereum-solana-optimism-and-starknet-2024-2025-269i</link>
      <guid>https://forem.com/extropy/an-analysis-of-arbitrage-markets-across-ethereum-solana-optimism-and-starknet-2024-2025-269i</guid>
      <description>&lt;p&gt;The maturation of public blockchains into global financial layers has fundamentally altered the nature of arbitrage. In the nascent years of DeFi  arbitrage was a game of simple scripts and public mempools a competitive but largely amateur pursuit. &lt;br&gt;
By 2025, this activity has evolved into a hyper-specialised, institutional-grade industry that dictates the economic limits of blockchain scaling. MEV the total value that can be extracted from block production in excess of the standard block reward and gas fees, has become the dominant economic variable in the design and operation of these networks.&lt;/p&gt;

&lt;p&gt;In this article I want to analyse from technical and economic perspectives, the arbitrage landscapes on four distinct blockchain architectures: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ethereum Layer 1&lt;/li&gt;
&lt;li&gt;Solana&lt;/li&gt;
&lt;li&gt;Optimism &lt;/li&gt;
&lt;li&gt;Starknet &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each network represents a distinct "MEV Regime" a unique combination of consensus mechanisms, transaction ordering rules, and latency constraints that forces arbitrageurs to adopt radically different strategies.&lt;/p&gt;

&lt;p&gt;The analysis that follows is shaped by the following idea: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The structure of the underlying chain largely determines the behaviour of the bot&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Ethereum&lt;/strong&gt;, the separation of proposers and builders has turned arbitrage into a sealed-bid auction for inclusion. &lt;br&gt;
On &lt;strong&gt;Solana&lt;/strong&gt;, the lack of a mempool and the speed of block production have created a latency-sensitive streaming auction. &lt;br&gt;
On &lt;strong&gt;Optimism&lt;/strong&gt;, the combination of low fees and centralised sequencing has incentivised a "spam-as-strategy" equilibrium. &lt;br&gt;
On &lt;strong&gt;Starknet&lt;/strong&gt;, Zero-Knowledge proof generation times and a developing decentralisation roadmap create a temporary environment of slower, atomic a&lt;/p&gt;




&lt;p&gt;I have pulled together data and research from late 2024 and 2025, to quantify the number of active bots, their operational costs, and their expected profitability. &lt;br&gt;
I also explore the structural shift from "code is law" to "ordering is economy," where the ability to order transactions is the primary product being sold by validators and sequencers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MEV Trilemma in 2025
&lt;/h2&gt;

&lt;p&gt;Arbitrage strategies in the current market cycle are constrained by an "MEV Trilemma" that forces operators to optimise for two of three variables, often at the expense of the third. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution Certainty&lt;/strong&gt; : The probability that a transaction, once submitted, will be included in a block in the exact state required to capture profit. &lt;br&gt;
(High in Ethereum PBS; Low in Optimism FCFS).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capital Efficiency (Cost)&lt;/strong&gt; : The ratio of profit to the cost of execution, including failed attempts and infrastructure overhead. &lt;br&gt;
(High in Solana; Low in Ethereum due to high gas/bribes).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency Sensitivity&lt;/strong&gt;: The speed required to identify and capture an opportunity before a competitor. &lt;br&gt;
(Critical in Solana and L2s; Less critical in Ethereum PBS due to bundle auctions).&lt;/p&gt;

&lt;p&gt;Lets dig into the structure of the chains to shed light on this behaviour&lt;/p&gt;

&lt;h2&gt;
  
  
  Ethereum Layer 1: The Oligopoly of the Auction House
&lt;/h2&gt;

&lt;p&gt;Ethereum L1 remains the undisputed "heavyweight" arena of global arbitrage. &lt;br&gt;
While transaction volumes may be lower than high-throughput L1s or L2s, the value per transaction and the depth of liquidity create the largest absolute profit pool. However, the ecosystem has developed into a rigid hierarchy defined by Proposer-Builder Separation , where the "wild west" of priority gas auctions has been replaced by sophisticated supply chains.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Supply Chain Architecture: Builders, Relays, and Searchers
&lt;/h3&gt;

&lt;p&gt;To understand arbitrage on Ethereum in 2025, we must move away from the notion of the public mempool as the primary battleground. The vast majority of arbitrage transactions over 90% are now routed through private channels known as MEV-Boost. This system separates the role of the Proposer from the Builder.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Searchers&lt;/strong&gt; run the algorithms that detect price discrepancies. &lt;br&gt;
In 2025, searchers do not broadcast transactions to the public network. Instead, they submit "bundles"—atomic packages of transactions to builders. A bundle might contain a user's transaction followed immediately by the searcher's back run transaction. &lt;br&gt;
The searcher attaches a  direct payment to the builder to this bundle. &lt;br&gt;
If the profit is say $100, the searcher might bid $90, keeping $10 as profit. &lt;br&gt;
This high "bid-to-profit" ratio is the defining characteristic of Ethereum arbitrage.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Builders&lt;/strong&gt; are the power brokers of the Ethereum network. &lt;br&gt;
They aggregate bundles from thousands of searchers and optimise them to create the most profitable block possible. The market for block building has become heavily centralised. &lt;br&gt;
Research indicates that by early 2025, the top two builders capture over 90% of block auctions. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relays&lt;/strong&gt; are the trusted intermediaries that pass the block headers from builders to validators, ensuring that the validator cannot steal the MEV inside the block.&lt;/p&gt;

&lt;p&gt;This architecture means that for an arbitrage bot to be competitive on Ethereum, its primary "skill" is not just identifying the arbitrage opportunity, but correctly pricing the bribe. &lt;/p&gt;

&lt;p&gt;A bot that consistently underbids will never have its transactions included. &lt;br&gt;
A bot that overbids operates at a loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bot Population and Market Concentration
&lt;/h3&gt;

&lt;p&gt;Contrary to the popular image of thousands of bots competing for every trade, the viable professional arbitrage market on Ethereum is surprisingly small and concentrated. &lt;br&gt;
Empirical analysis of MEV searchers reveals that in any given week, the number of unique "core" entities, those consistently winning bids and generating significant profit, often does not exceed 20.   &lt;/p&gt;

&lt;p&gt;This concentration is driven by the intense capital and technical barriers to entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capital Requirements&lt;/strong&gt;: To win a bundle auction, a searcher must often hold significant inventory of assets to execute the trade atomically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Costs&lt;/strong&gt;: While RPC costs might run $200-$500 monthly , the real cost is in the research and development of proprietary pricing models and simulation engines that allow a bot to bid 99% of the profit margin without going underwater.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Long Tail&lt;/strong&gt;: While there are hundreds of active addresses attempting arbitrage, the vast majority are "peripheral" participants who capture sporadic, lower-value opportunities or operate strategies that do not require fighting for the top-of-block position.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profitability Analysis: The "Black Monday" Case Study
&lt;/h3&gt;

&lt;p&gt;The scale of profits on Ethereum can be staggering during periods of volatility, but these windfalls are captured by a select few. A look at the market turmoil on August 5, 2024 ("Black Monday"), provides a granular view of potential earnings.&lt;/p&gt;

&lt;p&gt;On this single day, a specific builder labelled MEV Builder 0x3b secured 1,448 ETH in rewards, valued at approximately $3.5 million at the time. This revenue was not generated by a single trade but by constructing blocks containing massive liquidations and arbitrage opportunities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liquidation Efficiency&lt;/strong&gt;: In Block 20459000, a liquidation event sent 358.7 ETH ($802,000) to the builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arbitrage Integration&lt;/strong&gt;: Following this liquidation, an arbitrage bot executed a trade that paid 36 ETH to the builder.&lt;/p&gt;

&lt;p&gt;This illustrates the "food chain" nature of Ethereum MEV. &lt;br&gt;
The arbitrage bot (the Searcher) likely made a gross profit significantly higher than 36 ETH, but it was forced to pay that 36 ETH to the builder to ensure its transaction was included. &lt;br&gt;
The builder, in turn, paid a portion of that to the validator. &lt;br&gt;
The searcher's net profit is the remainder. &lt;br&gt;
Data from ESMA and EigenPhi supports this, suggesting that searchers often pay more than 90% of their revenue to proposers.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Strategic Nuances: Sandwiching vs. Atomic Arb
&lt;/h3&gt;

&lt;p&gt;Two primary strategies dominate the Ethereum landscape in 2025:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomic Arbitrage&lt;/strong&gt;: The simultaneous buying and selling of an asset across different venues (e.g., Uniswap vs. SushiSwap) within a single transaction. This is risk-free in terms of market movement but carries execution risk (gas costs if the bundle fails, though Flashbots protects against this).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sandwich Attacks&lt;/strong&gt;: This controversial strategy involves placing a transaction before and after a victim's pending trade. &lt;br&gt;
Despite improved user protections (like RPCs that hide transactions from the public mempool), sandwiching remains highly profitable. &lt;br&gt;
Sophisticated bots continue to exploit users who set high slippage tolerances on DEXs. &lt;/p&gt;

&lt;p&gt;However, the "low-hanging fruit" has diminished, forcing bots to target more complex, multi-hop trades.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure Costs and Operational Overhead
&lt;/h3&gt;

&lt;p&gt;Running a competitive arbitrage operation on Ethereum is capital intensive compared to other chains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gas Fees&lt;/strong&gt;: The primary operating cost. &lt;br&gt;
Even with Layer 2 scaling, L1 gas fees can range from $5 to $50 per transaction during congestion, and significantly higher during "gas wars".   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node Infrastructure&lt;/strong&gt;: Competitive searchers run their own nodes or pay for premium, low-latency RPC services. The cost for a dedicated Ethereum node setup can range from hundreds to thousands of dollars monthly depending on the provider and the level of geographic distribution required.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simulation&lt;/strong&gt;: Successful bots run complex off-chain simulations of the Ethereum state to calculate the exact outcome of a trade before submitting the bundle. This requires substantial compute power, adding to the monthly burn rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solana: The High-Frequency Trading Floor
&lt;/h2&gt;

&lt;p&gt;If Ethereum is an auction house, Solana is a high-frequency trading floor. &lt;br&gt;
The network's architecture characterised by 400ms block times, the absence of a public mempool, and a unique "Leader Schedule" creates a fundamentally different environment for arbitrageurs. &lt;br&gt;
Between 2024 and 2025, Solana underwent a radical transformation from a network plagued by spam to one disciplined by the Jito auction mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Gulf Stream and The Leader Schedule
&lt;/h3&gt;

&lt;p&gt;Unlike Ethereum, where transactions wait in a mempool to be picked up, Solana forwards transactions directly to the "Leader" the specific validator scheduled to produce the next block. &lt;br&gt;
This protocol, known as Gulf Stream, eliminates the global mempool and theoretically reduces latency.&lt;br&gt;
However, in the absence of a fee market, this design historically encouraged "spam as a service." &lt;br&gt;
Arbitrage bots would flood the Leader with thousands of duplicate transaction requests, hoping that one would be processed first. &lt;br&gt;
This often caused network congestion and outages.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Jito Revolution: Ordering as a Product
&lt;/h3&gt;

&lt;p&gt;The introduction and widespread adoption of the Jito-Solana client has been the single most significant development in Solana's MEV ecosystem. &lt;br&gt;
By early 2025, validators running Jito software control over 92% of the network stake.&lt;br&gt;&lt;br&gt;
Jito creates an out-of-protocol auction mechanism similar to Flashbots but optimised for Solana's speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bundles&lt;/strong&gt; : Searchers submit bundles of transactions with a guaranteed tip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Auction&lt;/strong&gt;: Jito's Block Engine simulates these bundles and forwards the most profitable ones to the validator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spam Reduction&lt;/strong&gt;: Because the auction happens off-chain and only the winning bundle is submitted, the incentive to spam the network is drastically reduced for Jito-connected validators.&lt;/p&gt;

&lt;p&gt;The economic impact has been profound. &lt;br&gt;
In April 2023, Jito tips accounted for only ~10% of priority fees. &lt;br&gt;
By early 2025, they exceed 60%, indicating that professional arbitrageurs have almost entirely migrated to this auction model.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Bot Ecology: The "Whales" of Solana
&lt;/h3&gt;

&lt;p&gt;The Solana arbitrage market is dominated by a few hyper-active entities. On-chain forensic analysis has identified specific bot addresses that capture massive market share.&lt;/p&gt;

&lt;h4&gt;
  
  
  The "E6Y" Bot Case Study:
&lt;/h4&gt;

&lt;p&gt;One specific bot, identified by the address prefix E6YoRP..., has been observed capturing 42% of the entire sandwich attack volume on Solana.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume&lt;/strong&gt;: In a single 30-day period, this bot executed trades worth over $1.6 billion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revenue&lt;/strong&gt;: The bot generated a gross revenue of 57,400 SOL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;: It paid 2.8 SOL in transaction fees and a staggering 7,980 SOL in Jito tips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Net Profit&lt;/strong&gt;: The operation netted approximately 49,400 SOL, which translates to roughly $300,000 per day at the relevant market prices.&lt;/p&gt;

&lt;h4&gt;
  
  
  Other Major Players:
&lt;/h4&gt;

&lt;p&gt;The second-largest bot (89Ny...) processed $433 million in volume (11.2% market share).&lt;br&gt;
The third-largest (B91...) processed $277 million (7.2% market share).   &lt;/p&gt;

&lt;p&gt;Another case study of the B91 sandwich bot showed it extracting 7,800 SOL in gross profit over 30 days, victimising nearly 78,800 retail traders.   &lt;/p&gt;

&lt;p&gt;These figures illustrate a "power law" distribution where the top 3 bots control over 60% of the market. &lt;br&gt;
This concentration is likely due to the extreme technical difficulty of building low-latency infrastructure that can compete in 400ms block times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Aggregate Market Statistics
&lt;/h3&gt;

&lt;p&gt;Broader market data confirms the scale of arbitrage on Solana.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction Count&lt;/strong&gt;: Jito's detection algorithms identified over 90 million successful arbitrage transactions over a one-year period ending in 2025.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total Profit&lt;/strong&gt;: These transactions generated $142.8 million in profits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Average Profit&lt;/strong&gt;: The average profit per arbitrage transaction is significantly lower than Ethereum, sitting at just $1.58. &lt;br&gt;
This confirms the "high frequency, low margin" nature of Solana MEV compared to Ethereum's "low frequency, high margin" model.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure and Costs
&lt;/h3&gt;

&lt;p&gt;Running a competitive Solana bot is less about gas costs and more about hardware and connectivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RPC Costs&lt;/strong&gt;: High-performance, dedicated RPC nodes are a prerequisite. Providers like RPC Fast, Triton One, and Helius offer dedicated nodes ranging from $1,800 to $3,800 per month.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Co-location&lt;/strong&gt;: The most sophisticated searchers co-locate their servers in the same data centres as major validators to shave milliseconds off transmission time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Geyser Plugins&lt;/strong&gt;: Advanced bots use "Geyser Plugins" to subscribe directly to account updates from the validator, bypassing the standard RPC overhead entirely.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Optimism and The Superchain: The Paradox of "Spam-Based Arbitrage"
&lt;/h2&gt;

&lt;p&gt;The arbitrage landscape on Optimism, and the broader "Superchain" ecosystem (including Base), presents a fascinating economic paradox in 2025. While these networks are designed to be efficient and low-cost, the very cheapness of their block space—combined with a centralised sequencing model—has birthed a "spam-as-strategy" equilibrium.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Priority Fails" Phenomenon
&lt;/h3&gt;

&lt;p&gt;A groundbreaking research paper titled "When Priority Fails"  provides the definitive analysis of arbitrage behaviour on Optimistic Rollups in 2025. The research uncovers a counterintuitive reality:   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Mechanism&lt;/strong&gt;: Optimism uses a single centralised sequencer that orders transactions on a First-Come-First-Served (FCFS) basis (with some priority fee nuances).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Incentive&lt;/strong&gt;: The Dencun upgrade (EIP-4844) drastically reduced the cost of posting data to L1, making L2 transactions incredibly cheap (fractions of a cent).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Strategy&lt;/strong&gt;: Instead of building complex bidding systems to win a specific slot (like on Ethereum), arbitrage bots find it cheaper to simply flood the sequencer with duplicate transactions. &lt;br&gt;
If a bot wants to capture an arb, it might send the same transaction 50 times. It doesn't matter if 49 fail; the cost of failure is negligible compared to the potential profit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quantifying the Spam
&lt;/h3&gt;

&lt;p&gt;The data on this phenomenon is stark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revert Rates&lt;/strong&gt;: Following the Dencun upgrade, the rate of reverted transactions on L2s spiked from under 5% to over 10%.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composition&lt;/strong&gt;: 80% of these reverted transactions are swaps, and 50% target USDC-WETH pools on Uniswap v3/v4. This confirms that the reverts are not user errors but failed arbitrage attempts.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concentration&lt;/strong&gt;: The spam is not evenly distributed. On Base, a key member of the Optimism Superchain, just two searcher entities were responsible for more than 80% of all spam.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Sequencer Revenue and the "Invisible Tax"
&lt;/h3&gt;

&lt;p&gt;While this spam congests the network for users, it is highly profitable for the network operators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revenue Capture&lt;/strong&gt;: The centralised sequencer collects fees for every transaction, successful or failed. Reverted transactions contribute disproportionately to sequencer revenue. On Base, reverts generate roughly 20-25% of total priority fee revenue.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Welfare Transfer&lt;/strong&gt;: This dynamic represents a transfer of welfare from users (who experience slower networks) and unsuccessful searchers (who burn fees) to the Optimism Collective and Base.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Bot Profitability and Market Structure
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Volume vs. Users&lt;/strong&gt;: On-chain analysis of Optimism reveals a "tale of two metrics." Transaction counts have surged to all-time highs (1.88 million daily), while active address counts have remained stagnant at ~63,000. This divergence is a clear signal that the network's growth is being driven by bot activity rather than organic user adoption.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Profit Margins&lt;/strong&gt;: The barriers to entry for this strategy are incredibly low. Any developer with a script can spam the sequencer. This leads to a "race to the bottom" where margins are compressed to the point where the profit of the arb barely exceeds the cost of the spam. &lt;br&gt;
Unlike the oligopoly of Ethereum or the "whale" dominance of Solana, Optimism is a chaotic arena of high-volume, low-margin grinders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base Revenue Contribution
&lt;/h3&gt;

&lt;p&gt;The revenue generated by this activity flows back to the Optimism Collective. In October 2025, Base contributed 44.8% of the total revenue obtained by the Optimism Collective (244.9 ETH), while other chains contributed 13.8%. This highlights how critical the high-volume arbitrage economy on Base is to the sustainability of the broader Superchain.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Starknet: The ZK Frontier
&lt;/h2&gt;

&lt;p&gt;Starknet represents the frontier of arbitrage, operating under the constraints of a Zero-Knowledge Rollup architecture. &lt;br&gt;
The MEV landscape here is nascent, constrained by centralised sequencing and longer confirmation times, but is poised for radical change as the network decentralises.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unique Constraints: The Validity Proof Bottleneck
&lt;/h3&gt;

&lt;p&gt;Arbitrage on Starknet differs fundamentally from Optimism or Solana due to the mechanics of validity proofs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prover Latency&lt;/strong&gt;: Transactions must be proven valid before they are finalised on L1. While "soft finality" on L2 is faster, the overall cadence of the network is different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FCFS Ordering&lt;/strong&gt;: Like Optimism, Starknet currently uses a centralised sequencer with First-Come-First-Served ordering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No Mempool&lt;/strong&gt;: Starknet does not have a public mempool. Transactions are sent blindly to the sequencer. This makes "sandwiching" difficult because a bot cannot see a victim's transaction in a pool and insert its own around it.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Dominant Strategy: Atomic Arbitrage and "MAV"
&lt;/h3&gt;

&lt;p&gt;With sandwich attacks largely neutered by the architecture, the dominant strategy is pure Atomic Arbitrage (DEX-DEX or CEX-DEX).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-Domain Opportunities&lt;/strong&gt;: Bots monitor price discrepancies between Starknet AMMs (like Ekubo and Nostra) and centralised exchanges (CEXs). Because Starknet blocks are slower than a CEX order book, there is significant "stale" pricing on-chain that bots can exploit.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maximal Arbitrage Value (MAV)&lt;/strong&gt;: Researchers are using a new metric called MAV to quantify these opportunities, specifically looking at the lag between CEX price movements and AMM updates.   &lt;/p&gt;

&lt;h3&gt;
  
  
  Ecosystem Growth and Barriers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Technical Barrier&lt;/strong&gt;: The requirement to interact with Cairo smart contracts creates a moat. Standard Solidity bots cannot be simply copy-pasted onto Starknet; they must be rewritten, often requiring a deep understanding of the Cairo VM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Growth&lt;/strong&gt;: Despite these barriers, the ecosystem is expanding. Starknet grew from 72 to 193 user-centric projects in 2024, a 168% increase.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bot Population&lt;/strong&gt;: The active bot population is significantly smaller than the other chains, numbering in the dozens rather than the hundreds. However, as liquidity deepens, this is expected to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Decentralisation Roadmap (2025-2026)
&lt;/h3&gt;

&lt;p&gt;The most critical factor for Starknet's MEV future is its roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staking v2/v3&lt;/strong&gt;: Planned for late 2025, these upgrades will decentralise the sequencer and introduce a consensus mechanism.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MEV Market Emergence&lt;/strong&gt;: As the single sequencer is replaced by a network of validators, an MEV market will inevitably emerge to manage ordering. Whether this takes the form of an auction (like Jito) or a different mechanism will determine the future profitability of bots on the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparative Synthesis: Metrics and Outlook
&lt;/h2&gt;

&lt;p&gt;To visualise the stark differences between these ecosystems, the following data synthesise the key metrics derived from the research.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparative Metrics Table (2024-2025)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Ethereum L1&lt;/th&gt;
&lt;th&gt;Solana&lt;/th&gt;
&lt;th&gt;Optimism (Superchain)&lt;/th&gt;
&lt;th&gt;Starknet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Est. Monthly MEV Revenue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$180 Million&lt;/td&gt;
&lt;td&gt;~$45 Million&lt;/td&gt;
&lt;td&gt;Undisclosed (High Sequencer Revenue)&lt;/td&gt;
&lt;td&gt;Developing / Niche&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dominant Strategy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bundle Auctions (99% Success)&lt;/td&gt;
&lt;td&gt;Stream Auctions &amp;amp; Latency&lt;/td&gt;
&lt;td&gt;Probabilistic Spam (High Fail Rate)&lt;/td&gt;
&lt;td&gt;Atomic Arbitrage (Latency)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Active "Core" Bots&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;20 Entities (Oligopoly)&lt;/td&gt;
&lt;td&gt;Top 3 Bots = ~60% Market&lt;/td&gt;
&lt;td&gt;High Concentration (2 entities = 80% spam)&lt;/td&gt;
&lt;td&gt;Specialised / Low Count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Top Bot Earnings (Est.)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Millions/Day (Outliers)&lt;/td&gt;
&lt;td&gt;~$300k/Day (Top Sandwicher)&lt;/td&gt;
&lt;td&gt;Volume-driven, thin margins&lt;/td&gt;
&lt;td&gt;Moderate / Lower Liquidity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg. Profit Per Arb&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (\$100s - \$1000s)&lt;/td&gt;
&lt;td&gt;Low (~$1.58)&lt;/td&gt;
&lt;td&gt;Cents (offset by spam cost)&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Cost Driver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bribes to Builders (90%+ of Rev)&lt;/td&gt;
&lt;td&gt;Jito Tips + Hardware&lt;/td&gt;
&lt;td&gt;Gas for Failed Txs&lt;/td&gt;
&lt;td&gt;Proving/Gas Fees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (\$200-\$500 RPC + R&amp;amp;D)&lt;/td&gt;
&lt;td&gt;Very High (\$1.8k-\$3.8k RPC)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Strategic Landscape
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ethereum&lt;/strong&gt; is a "Whale's Game": The market is mature, calcified, and expensive. Success requires deep relationships with builders and massive capital. The "alpha" here is in complex, multi-block strategies and off-chain hedging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solana&lt;/strong&gt; is a "High-Frequency Trader's Game": It is the closest analogue to traditional finance (TradFi) markets. Success is determined by hardware, co-location, and the efficiency of the bidding algorithm in the Jito auction. The volume is massive (90M+ arbs/year), but the per-trade margin is razor-thin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimism&lt;/strong&gt; is a "Spammer's Game": It is a chaotic, unregulated market where the lack of an efficient auction mechanism forces participants to engage in wasteful behaviour. It is the most accessible market for new entrants but arguably the least efficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starknet&lt;/strong&gt; is the "Frontier": It offers the highest potential for "informational alpha" due to the technical barriers of Cairo and the ZK architecture, but lacks the deep liquidity of the other chains.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Outlook: The Convergence?
&lt;/h3&gt;

&lt;p&gt;As we look toward late 2025 and 2026, the distinctions may blur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-Chain MEV&lt;/strong&gt;: The rise of cross-chain bridges and unified liquidity layers (like LayerZero) is creating a new class of arbitrage that spans these networks. Bots are already beginning to arb the price difference between Solana (fast) and Ethereum (liquid).   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution to Spam&lt;/strong&gt;: The unsustainable nature of the spam on Optimism and Base will likely force the adoption of "Shared Sequencing" or decentralised auction mechanisms similar to Flashbots, bringing order to the chaos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Institutionalisation&lt;/strong&gt;: Across all chains, the trend is clear. The solo developer is being squeezed out by capitalised firms that can afford the $3,000/month RPC nodes, the co-location fees, and the specialised engineering talent required to compete in a market where margins are measured in basis points and milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  In conclusion
&lt;/h2&gt;

&lt;p&gt;Arbitrage in 2025 is not a story of code, but of market structure. &lt;br&gt;
The bot is merely a rational actor optimising for the specific constraints be they auctions, leader schedules, or sequencer latencies imposed by the chain it inhabits.&lt;/p&gt;

&lt;p&gt;Originally published on: &lt;a href="https://academy.extropy.io/pages/articles/mev-crosschain-analysis-2025.html" rel="noopener noreferrer"&gt;academy.extropy.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>ethereum</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>A Developer's Guide to Implementing the x402 Protocol</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Thu, 11 Dec 2025 15:53:15 +0000</pubDate>
      <link>https://forem.com/extropy/a-developers-guide-to-implementing-the-x402-protocol-2dn</link>
      <guid>https://forem.com/extropy/a-developers-guide-to-implementing-the-x402-protocol-2dn</guid>
      <description>&lt;p&gt;This guide follows on from our previous article about EIP-3009, you can read that &lt;a href="https://academy.extropy.io/pages/articles/review-eip-3009.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;For developers, the x402 protocol is a practical toolset for monetising web services. It offers a direct alternative to cumbersome API key management and inflexible subscription models, enabling a true, pay-per-use economy.&lt;/p&gt;

&lt;p&gt;At its core, x402 is an open-source standard that activates the long-dormant &lt;code&gt;HTTP 402 "Payment Required"&lt;/code&gt; status code. It provides a chain-agnostic choreography for an AI agent or client to pay for a resource, like an API call, at the very moment it is requested.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Server-Side: "One Line of Code" Monetisation
&lt;/h2&gt;

&lt;p&gt;If you are a developer looking to monetise an API (the "supply" side), the integration is simple. Using middleware libraries for popular frameworks, you can payment-gate an endpoint with just a single line of code.&lt;/p&gt;

&lt;p&gt;For a Node.js server (using Express for example), the implementation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This example shows how to protect an endpoint and set a price&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;paymentMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xYourWalletAddress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/your-premium-endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0.01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// That's it. This endpoint is now monetised.&lt;/span&gt;
&lt;span class="nx"&gt;app&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/your-premium-endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret premium data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This middleware automatically handles the entire payment negotiation, verification, and settlement flow, allowing you to focus on your application's logic.&lt;/p&gt;

&lt;p&gt;So what is happening behind the scenes ? &lt;/p&gt;

&lt;h2&gt;
  
  
  The Core x402 Handshake
&lt;/h2&gt;

&lt;p&gt;While the middleware makes it simple, it's crucial to understand the three-phase HTTP handshake that happens under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 1: Request → Quote&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Initial Request:&lt;/strong&gt; The client (which could be an AI agent) makes a standard &lt;code&gt;GET&lt;/code&gt; request to your protected resource (&lt;code&gt;/your-premium-endpoint&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Payment Required:&lt;/strong&gt; Your server's middleware intercepts this. Seeing no payment, it returns an &lt;code&gt;HTTP 402: Payment Required&lt;/code&gt; status.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Quote:&lt;/strong&gt; The &lt;em&gt;body&lt;/em&gt; of this 402 response is a JSON payload. This is the "bill," specifying the &lt;code&gt;PaymentRequirements&lt;/code&gt;, such as the price (0.01 USDC), the supported network (e.g., Base), and the facilitator's API endpoint.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 2: Pay → Verify&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Sign Payload:&lt;/strong&gt; The client's logic (see below) receives the 402, parses the JSON "bill," and creates a payment payload. It then &lt;em&gt;signs this payload off-chain&lt;/em&gt; using its private key.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Retry with Payment:&lt;/strong&gt; The client retries the original request. This time, it includes a new HTTP header: &lt;code&gt;X-PAYMENT&lt;/code&gt;. The value of this header is the Base64-encoded JSON &lt;code&gt;Payment Payload&lt;/code&gt;, which contains the signed authorisation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 3: Settle → Deliver&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Verify Payload:&lt;/strong&gt; Your server's middleware intercepts the new request. It doesn't need to understand crypto; it simply forwards the &lt;code&gt;X-PAYMENT&lt;/code&gt; payload to the specified facilitator's &lt;code&gt;POST /verify&lt;/code&gt; endpoint. The facilitator cryptographically verifies the signature and returns a simple &lt;code&gt;{"isValid": true}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Settle &amp;amp; Deliver:&lt;/strong&gt; Once verified, the server does two things in parallel: it fulfils the request (granting access to the data) and sends the payload to the facilitator's &lt;code&gt;POST /settle&lt;/code&gt; endpoint to execute the on-chain transfer.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Confirmation:&lt;/strong&gt; The server returns the final &lt;code&gt;HTTP 200 OK&lt;/code&gt; response to the client, containing the requested resource. It also includes an &lt;code&gt;X-PAYMENT-RESPONSE&lt;/code&gt; header, which contains the transaction hash as a receipt.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Role of the Facilitator
&lt;/h2&gt;

&lt;p&gt;The "magic" that makes the server-side implementation so simple is the &lt;strong&gt;facilitator&lt;/strong&gt;. This is a third-party service that acts as the bridge between your HTTP-native server and the blockchain.&lt;/p&gt;

&lt;p&gt;Your server remains crypto-agnostic. It never holds keys, runs a node, or pays for gas. It simply makes two stateless REST API calls (&lt;code&gt;/verify&lt;/code&gt; and &lt;code&gt;/settle&lt;/code&gt;) to a trusted facilitator (like those provided by Coinbase, Meridian, or &lt;a href="https://mogami.tech/" rel="noopener noreferrer"&gt;Mogami&lt;/a&gt;), which handles all the on-chain complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Client-Side: Building an x402-Aware Agent
&lt;/h2&gt;

&lt;p&gt;While the server-side is simple, the client bears the burden of complexity. Developers building agents must integrate client-side SDKs like &lt;code&gt;@coinbase/x402-sdk&lt;/code&gt; and manage wallet capabilities.&lt;/p&gt;

&lt;p&gt;The agent's core logic must be architected as a state machine capable of handling three scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Free Requests:&lt;/strong&gt; Standard handling for &lt;code&gt;response.ok&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Payment-Gated Requests:&lt;/strong&gt; The primary logic path, triggered by &lt;code&gt;response.status === 402&lt;/code&gt;. The agent must parse the 402, use its wallet to sign the payment, and manage the stateful retry with the &lt;code&gt;X-PAYMENT&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Payment Failures:&lt;/strong&gt; Graceful error handling if the payment fails or the service fails after payment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The "Gasless" Magic: How EIP-3009 is Used
&lt;/h2&gt;

&lt;p&gt;The "gasless" client experience is another key technical component. The client doesn't need the network's native gas token (e.g., ETH) to make a payment.&lt;/p&gt;

&lt;p&gt;This is enabled by the &lt;strong&gt;EIP-3009&lt;/strong&gt; standard (&lt;code&gt;transferWithAuthorization&lt;/code&gt;). The client doesn't submit a transaction; it simply &lt;em&gt;signs&lt;/em&gt; an off-chain authorisation message. The facilitator takes this signed message and submits it to the blockchain, &lt;em&gt;paying the gas fee&lt;/em&gt; on the client's behalf. &lt;br&gt;
This abstraction is what makes autonomous, high-frequency micropayments feasible.&lt;/p&gt;

&lt;p&gt;Ultimately, x402 is a lightweight and open standard that provides the essential choreography for on-chain value exchange, finally turning the web's original, dormant "Payment Required" code into a live, usable rail for developers.&lt;/p&gt;

</description>
      <category>x402</category>
      <category>blockchain</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>An Overview of EIP-3009: Transfer With Authorisation</title>
      <dc:creator>Erick Fernandez</dc:creator>
      <pubDate>Tue, 09 Dec 2025 18:41:43 +0000</pubDate>
      <link>https://forem.com/extropy/an-overview-of-eip-3009-transfer-with-authorisation-3j50</link>
      <guid>https://forem.com/extropy/an-overview-of-eip-3009-transfer-with-authorisation-3j50</guid>
      <description>&lt;p&gt;I've never been good with the EIP numbers, so if you are like me and wondering what this is about, this EIP is about making authorisation and transfer more user friendly.&lt;/p&gt;

&lt;p&gt;One of the most persistent points of friction for web3 devs has been the user experience of on-chain transactions. EIP-3009, titled "Transfer With Authorisation," is a crucial (though still "Draft" status) protocol that directly tackles this problem. &lt;/p&gt;

&lt;p&gt;While often discussed alongside other standards, EIP-3009 is a specific ERC-20 extension that, once implemented by a token, provides a new way to move assets. Its adoption by major stablecoins like USDC has made it a foundational component for the emerging machine-to-machine economy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Gas Fees and Clunky UX
&lt;/h2&gt;

&lt;p&gt;Before EIP-3009, paying for something with an ERC-20 token was a famously clunky, two-step process for a user:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The &lt;code&gt;approve&lt;/code&gt; Transaction:&lt;/strong&gt; A user must first send a transaction to the token contract, calling the &lt;code&gt;approve&lt;/code&gt; function to allow a smart contract to spend tokens on their behalf. This costs a gas fee.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The &lt;code&gt;transferFrom&lt;/code&gt; Transaction:&lt;/strong&gt; The user (or the smart contract) must then send a &lt;em&gt;second&lt;/em&gt; transaction, calling &lt;code&gt;transferFrom&lt;/code&gt; to actually move the tokens. This costs &lt;strong&gt;another&lt;/strong&gt; gas fee.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This flow presents two critical problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Bad User Experience:&lt;/strong&gt; It requires two separate transactions, two gas fees, and two wallet pop-ups.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Gas Token Problem:&lt;/strong&gt; The user &lt;em&gt;must&lt;/em&gt; hold the network's native token (e.g., ETH on Ethereum) to pay for gas, even if they only want to spend their USDC. This is a massive onboarding barrier for new users and a non-starter for autonomous AI agents that cannot easily manage multiple wallet balances.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How EIP-3009 Works: The "Gasless" Authorisation
&lt;/h2&gt;

&lt;p&gt;EIP-3009 solves this by introducing the concept of meta-transactions for transfers. Instead of sending an on-chain &lt;code&gt;approve&lt;/code&gt; transaction, the user signs an off-chain message that authorises a transfer.&lt;/p&gt;

&lt;p&gt;This flow is designed to be handled by a third-party "relayer" or "facilitator."&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Off-Chain Signing (The Client)&lt;/strong&gt;&lt;br&gt;
The user (or an AI agent's wallet) does not create a transaction. Instead, it creates a structured, EIP-712 compliant typed message. This message contains the precise details of the transfer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The token holder's address (&lt;code&gt;from&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  The recipient's address (&lt;code&gt;to&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;value&lt;/code&gt; to be transferred.&lt;/li&gt;
&lt;li&gt;  A &lt;code&gt;validAfter&lt;/code&gt; and &lt;code&gt;validBefore&lt;/code&gt; timestamp (to prevent replay attacks).&lt;/li&gt;
&lt;li&gt;  A unique, random &lt;code&gt;nonce&lt;/code&gt; (a &lt;code&gt;bytes32&lt;/code&gt; hash).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client signs this message with its private key, producing a cryptographic signature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Step 2: The Relayer (The Facilitator)&lt;/strong&gt;&lt;br&gt;
The client sends this signed message (the authorisation) to a relayer. This relayer can be any service willing to submit the transaction and pay the gas fee (e.g., the x402 facilitator).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 3: On-Chain Execution (The Contract)&lt;/strong&gt;&lt;br&gt;
The relayer takes the signed message and calls a new function on the ERC-20 contract: &lt;code&gt;transferWithAuthorization(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This function accepts the signed message parameters (&lt;code&gt;from&lt;/code&gt;, &lt;code&gt;to&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, &lt;code&gt;signature&lt;/code&gt;, etc.). The token contract itself then performs the following logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  It reconstructs the EIP-712 message.&lt;/li&gt;
&lt;li&gt;  It uses &lt;code&gt;ecrecover&lt;/code&gt; to verify that the signature matches the &lt;code&gt;from&lt;/code&gt; address.&lt;/li&gt;
&lt;li&gt;  It checks that the &lt;code&gt;nonce&lt;/code&gt; has not been used before.&lt;/li&gt;
&lt;li&gt;  It confirms the current time is within the &lt;code&gt;validAfter&lt;/code&gt; and &lt;code&gt;validBefore&lt;/code&gt; window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all checks pass, the contract executes the &lt;code&gt;transfer&lt;/code&gt; internally. The user's tokens are moved, and the relayer (who called the function) pays the gas fee.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Technical Features for Developers
&lt;/h2&gt;

&lt;p&gt;For developers, two features of EIP-3009 are particularly important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Atomic Transfer:&lt;/strong&gt; Unlike EIP-2612 (&lt;code&gt;permit&lt;/code&gt;), which only authorises an &lt;em&gt;approval&lt;/em&gt;, EIP-3009 authorises the &lt;em&gt;entire transfer&lt;/em&gt;. It is a single-call execution.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Non-Sequential Nonces:&lt;/strong&gt; A traditional Ethereum account uses a sequential nonce (0, 1, 2, 3...). This creates a bottleneck, as you cannot process transaction 3 until 2 is confirmed. EIP-3009's nonce is a random &lt;code&gt;bytes32&lt;/code&gt; hash. 
This is a critical design choice for high-frequency systems, as it allows an agent to generate &lt;em&gt;thousands&lt;/em&gt; of concurrent, independent payment authorisations without any of them conflicting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: A Fragmented Standard
&lt;/h2&gt;

&lt;p&gt;If you are thinking we have been here before, well yes, there have been a few attempts to solve this problem.&lt;br&gt;
In fact EIP-3009's primary weakness is not technical but strategic: it is not the only standard. This has fragmented the ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;EIP-3009 (&lt;code&gt;transferWithAuthorization&lt;/code&gt;):&lt;/strong&gt; The standard for gasless &lt;em&gt;transfers&lt;/em&gt;. Implemented by Circle for USDC (v2).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;EIP-2612 (&lt;code&gt;permit&lt;/code&gt;):&lt;/strong&gt; A similar but incompatible standard for gasless &lt;em&gt;approvals&lt;/em&gt;. Implemented by Maker for DAI.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No Standard:&lt;/strong&gt; Tether (USDT), the largest stablecoin by market cap, implements &lt;em&gt;neither&lt;/em&gt; standard and has stated it has no plans to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This "token exclusivity" is the main limitation of protocols that rely on EIP-3009. While it provides a seamless experience for USDC, it cannot (by itself) support a truly chain-agnostic or token-agnostic payment layer, as it excludes a massive portion of the stablecoin market.&lt;br&gt;
Despite this, I wanted to introduce this EIP to you as background for some our next articles about the x402 protocol. &lt;/p&gt;

&lt;p&gt;Originally published on: &lt;a href="https://academy.extropy.io/pages/articles/review-eip-3009.html" rel="noopener noreferrer"&gt;academy.extropy.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ux</category>
      <category>ethereum</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
