<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ehsan Mahmoudi</title>
    <description>The latest articles on DEV Community by Ehsan Mahmoudi (@jeyem).</description>
    <link>https://dev.to/jeyem</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3998731%2F4822addd-7594-4c0e-bcf0-2f37e3359709.jpg</url>
      <title>DEV Community: Ehsan Mahmoudi</title>
      <link>https://dev.to/jeyem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeyem"/>
    <language>en</language>
    <item>
      <title>A Small Fintech Challenge, and Why I Let Postgres Hold the Money</title>
      <dc:creator>Ehsan Mahmoudi</dc:creator>
      <pubDate>Mon, 24 Aug 2026 08:43:29 +0000</pubDate>
      <link>https://dev.to/jeyem/a-small-fintech-challenge-and-why-i-let-postgres-hold-the-money-k4a</link>
      <guid>https://dev.to/jeyem/a-small-fintech-challenge-and-why-i-let-postgres-hold-the-money-k4a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📌 Originally published at &lt;a href="https://e-mahmoudi.me/blog/a-small-fintech-challenge-and-why-i-let-postgres-hold-the-money/" rel="noopener noreferrer"&gt;e-mahmoudi.me&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  A Small Fintech Challenge, and Why I Let Postgres Hold the Money
&lt;/h1&gt;

&lt;p&gt;I picked up a small backend challenge recently: build a marketplace where guilds buy and auction items using in-game gold. Listings, auctions, bids, a price oracle, automatic settlement. On the surface it's a CRUD app.&lt;/p&gt;

&lt;p&gt;The full source is on GitHub: &lt;a href="https://github.com/jeyem/dragon-market" rel="noopener noreferrer"&gt;github.com/jeyem/dragon-market&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It isn't. The moment money moves and two people act at the same time, it becomes the only problem that matters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A marketplace can lose a feature and survive. It cannot lose a coin and survive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two guilds bid on the same auction in the same millisecond. A buyer spends gold they already spent on something else a microsecond earlier. An auction settles twice because a retry fired. Every one of these is a money bug, and every one of them is a &lt;em&gt;race&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The interesting part of the project wasn't the API. It was deciding &lt;strong&gt;who is responsible for correctness&lt;/strong&gt;. I decided it shouldn't be my Go code. It should be Postgres.&lt;/p&gt;




&lt;h1&gt;
  
  
  The temptation, and why I skipped it
&lt;/h1&gt;

&lt;p&gt;The instinct is to reach for application-level concurrency control: a mutex around the bid handler, an in-memory queue per auction, a distributed lock in Redis. It feels like control.&lt;/p&gt;

&lt;p&gt;It's also a trap. App-level locks don't survive a second instance of your service. They don't survive a crash mid-operation. They don't roll back. And they sit &lt;em&gt;outside&lt;/em&gt; the system that actually stores the truth, so they're always one deploy away from being bypassed.&lt;/p&gt;

&lt;p&gt;Postgres already solves this, and it solves it with guarantees that have been hardened for decades. So the whole design became one sentence: &lt;strong&gt;every operation that touches money is a single database transaction, and the database serializes the parts that conflict.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Balances you can't corrupt because they don't exist
&lt;/h1&gt;

&lt;p&gt;The first decision was to not store balances at all.&lt;/p&gt;

&lt;p&gt;There's no &lt;code&gt;balance&lt;/code&gt; column anywhere. A wallet is &lt;em&gt;derived&lt;/em&gt; from an append-only ledger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total     = sum(grant, credit) - sum(debit)
reserved  = sum(reserve)       - sum(release)
available = total - reserved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sounds like a performance compromise. It's actually a correctness gift. A mutable balance column is the classic lost-update bug waiting to happen: read 100, read 100, both subtract 30, both write 70, and 30 gold just evaporated. You cannot have that bug against a number you never update. You only ever &lt;em&gt;append&lt;/em&gt; a fact — "reserved 50 for this bid" — and the balance is whatever the facts add up to. Every coin is traceable to a row.&lt;/p&gt;




&lt;h1&gt;
  
  
  Letting the database serialize the race
&lt;/h1&gt;

&lt;p&gt;The bids are where concurrency gets real. A new bid has to be at least 5% above the current highest, and the bidder has to actually have the funds. Both checks are meaningless if another bid lands between the read and the write.&lt;/p&gt;

&lt;p&gt;So the transaction takes a &lt;strong&gt;row lock&lt;/strong&gt; on the auction before it reads anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;auctions&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;FOR UPDATE&lt;/code&gt; makes Postgres serialize every bid on that one auction. The second bidder simply &lt;em&gt;waits&lt;/em&gt; — inside the database, holding a real lock — until the first transaction commits or rolls back. By the time they read the highest bid, it's the truth, not a stale snapshot. The 5% rule and the funds check are evaluated against reality and can't be undercut by a concurrent writer.&lt;/p&gt;

&lt;p&gt;The same pattern guards a buyer's wallet: lock the guild row, then check available funds and the daily spend cap. No two purchases can both believe the same gold is available.&lt;/p&gt;

&lt;p&gt;This is a deliberate trade. Bids on a &lt;em&gt;single&lt;/em&gt; auction now run one at a time, so a wildly hot auction is a throughput hotspot. I kept it anyway and wrote the trade-off down: it's per-row, so different auctions still run fully in parallel, and I'd take "slower but always correct" over "fast and occasionally wrong" with money every single time. Consistency first; throughput is an optimization you can do later with a clear conscience.&lt;/p&gt;




&lt;h1&gt;
  
  
  The bug the tests caught (and what it taught me)
&lt;/h1&gt;

&lt;p&gt;I wrapped the database calls in a circuit breaker, and my first version counted &lt;em&gt;business&lt;/em&gt; rejections — "bid too low", "insufficient funds" — as breaker failures. A burst of perfectly valid rejections tripped the breaker, and the next legitimate request got a 500.&lt;/p&gt;

&lt;p&gt;The end-to-end tests caught it immediately: a buy that should have returned &lt;code&gt;200&lt;/code&gt; came back &lt;code&gt;500&lt;/code&gt;. The fix was a one-line distinction — only real infrastructure faults (a failed begin or commit) trip the breaker; a rolled-back business rule counts as a &lt;em&gt;success&lt;/em&gt;, because the system did exactly what it should. The transaction had already protected the data. The breaker just had the wrong opinion about what "failure" means.&lt;/p&gt;




&lt;h1&gt;
  
  
  The takeaway
&lt;/h1&gt;

&lt;p&gt;The lesson I keep relearning: &lt;strong&gt;the database is not just where data rests, it's a correctness engine, and most of the time it's a better one than the code you'd write to replace it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ACID transactions gave me atomicity for free — debit the buyer, credit the seller, transfer the item, all or nothing. Row-level locking gave me serialization exactly where conflicts happen and nowhere else. An append-only ledger made an entire category of bug structurally impossible.&lt;/p&gt;

&lt;p&gt;I wrote less concurrency code, not more. The hard part — making races safe — I handed to the system designed for it. That's not laziness. On anything that touches money, it's the most senior decision in the whole project.&lt;/p&gt;

&lt;p&gt;The code, the migrations, and the end-to-end tests are all here: &lt;a href="https://github.com/jeyem/dragon-market" rel="noopener noreferrer"&gt;github.com/jeyem/dragon-market&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://e-mahmoudi.me/blog/a-small-fintech-challenge-and-why-i-let-postgres-hold-the-money/" rel="noopener noreferrer"&gt;e-mahmoudi.me&lt;/a&gt;. I write about blockchain, backend, and software architecture — more at &lt;a href="https://e-mahmoudi.me/" rel="noopener noreferrer"&gt;e-mahmoudi.me&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>go</category>
      <category>fintech</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>I Built a Liquidity Sniper Bot for BNB Chain, and It Proved a Losing Game</title>
      <dc:creator>Ehsan Mahmoudi</dc:creator>
      <pubDate>Mon, 24 Aug 2026 08:43:27 +0000</pubDate>
      <link>https://dev.to/jeyem/i-built-a-liquidity-sniper-bot-for-bnb-chain-and-it-proved-a-losing-game-39</link>
      <guid>https://dev.to/jeyem/i-built-a-liquidity-sniper-bot-for-bnb-chain-and-it-proved-a-losing-game-39</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📌 Originally published at &lt;a href="https://e-mahmoudi.me/blog/i-built-a-liquidity-sniper-bot-for-bnb-chain-and-it-proved-a-losing-game/" rel="noopener noreferrer"&gt;e-mahmoudi.me&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  I Built a Liquidity Sniper Bot for BNB Chain, and It Proved a Losing Game
&lt;/h1&gt;

&lt;p&gt;I built a crypto trading bot.&lt;/p&gt;

&lt;p&gt;Not to get rich. I went in fairly sure that "easy money" bots are mostly a fantasy.&lt;/p&gt;

&lt;p&gt;I built it to &lt;strong&gt;find out for myself&lt;/strong&gt;, with code, with real money, with data instead of opinions.&lt;/p&gt;

&lt;p&gt;And it gave me a clean answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The bot works flawlessly. The market is the problem.&lt;br&gt;
The tokens that survive every defense you build are the ones engineered to survive every defense you build, and then trap you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the story of that project: what it does, how I took it all the way to live trading, and the exact mechanism by which it lost. The engineering was the easy part. The honesty was the point.&lt;/p&gt;




&lt;h1&gt;
  
  
  The idea I started with (and why it was wrong)
&lt;/h1&gt;

&lt;p&gt;My first instinct was the one almost everyone has: &lt;strong&gt;arbitrage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A liquidity pool prices BNB against USDT by a simple formula. When the price drifts a little, buy low here, sell high there, pocket the difference. A fast bot should print money on those gaps, right?&lt;/p&gt;

&lt;p&gt;It took very little reading to see why that is the hardest game in crypto, not the easiest:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Established pairs are efficiently priced. Any deviation is closed in the &lt;strong&gt;same block&lt;/strong&gt; by professional MEV bots running on private mempools and co-located infrastructure. By the time a retail bot sees the gap in the public mempool, a second or two later, it is gone, and your transaction is visible to everyone, so you get front-run too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Efficient markets have no retail edge. That edge was captured years ago by people with infrastructure and capital I can't match.&lt;/p&gt;

&lt;p&gt;So I reasoned my way to the one corner that might still be inefficient:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Brand-new token launches have &lt;strong&gt;no price discovery yet&lt;/strong&gt;. The gap between the launch price and where the market settles is the only mispricing a small bot could theoretically capture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is "sniping." And it led me straight into the most adversarial environment I have ever written software for.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I built
&lt;/h1&gt;

&lt;p&gt;I wrote it in &lt;strong&gt;Go&lt;/strong&gt;, as a clean, staged pipeline. The shape matters, because each stage is a defense, and the story is really about defenses being defeated one by one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebSocket (PairCreated event)
   -&amp;gt; listener     receive raw logs
   -&amp;gt; pipeline     a funnel of gates, cheap to expensive:
        decode -&amp;gt; reserve -&amp;gt; token -&amp;gt; score -&amp;gt; lp/pre-arm -&amp;gt; honeypot -&amp;gt; output -&amp;gt; trade
   -&amp;gt; monitor      track each position, run the exit strategy, execute sells
   -&amp;gt; executor     sign and submit real swaps (live mode)
   -&amp;gt; store        SQLite record of every position and sell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few pieces I am genuinely proud of as engineering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A pure-Go honeypot simulator.&lt;/strong&gt; Before buying, it runs a full buy-then-sell round trip against the real token using &lt;code&gt;eth_simulateV1&lt;/code&gt;: one RPC call, state carried between calls, a fabricated wallet funded by overriding a storage slot. If the simulated sell reverts, it is a honeypot, and we never touch it. No deployed helper contract, no Solidity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMM-accurate paper trading.&lt;/strong&gt; The sandbox prices fills along the real constant-product curve, so a rugged pool fills near zero. Paper P&amp;amp;L cannot hide a loss the way naive backtests do, which turned out to matter enormously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real on-chain executor.&lt;/strong&gt; Encrypted keystore wallet (passphrase from the environment, never disk), a capital ledger that gates buys on balance plus a gas reserve, real V2 swaps that measure tokens received from the actual &lt;code&gt;balanceOf&lt;/code&gt; delta, a deployer blocklist that learns from rugs, and restart-liquidation so a crash never strands a position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chain and DEX general.&lt;/strong&gt; Router, wrapped-native, chain id, and fees all come from config. The same binary runs any Uniswap-V2-style DEX on any EVM chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a complete, working system. That is important, because it means when it lost, it was not losing because of a bug.&lt;/p&gt;




&lt;h1&gt;
  
  
  Then I did the thing most people don't: I went live
&lt;/h1&gt;

&lt;p&gt;Paper trading looked great. In the sandbox, most tokens showed a profit.&lt;/p&gt;

&lt;p&gt;I almost stopped there. I am glad I didn't, because the gap between paper and live is the first real lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Paper exits are instant, free, and never fail. Live exits are a transaction that takes about three seconds to mine, into a market that is actively trying to trap you. &lt;strong&gt;Paper profitability does not transfer to live for this strategy.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I funded a throwaway wallet with a few dollars, set the trade size tiny (about $0.60 a pop), flipped the safety switch off, and let it run for real.&lt;/p&gt;

&lt;p&gt;Here is exactly what happened, defense by defense.&lt;/p&gt;




&lt;h1&gt;
  
  
  The field report
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The bot, at each stage&lt;/th&gt;
&lt;th&gt;What real money did&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Detection plus honeypot sim&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;12 of 12 losses, -83%.&lt;/strong&gt; Every token went flat, then rugged in 10 to 18 seconds.&lt;/td&gt;
&lt;td&gt;rugs dominate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;plus LP lock/burn check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One whole night: &lt;strong&gt;0 buys.&lt;/strong&gt; 66 launches skipped for unlocked liquidity.&lt;/td&gt;
&lt;td&gt;the safe ones are rare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;plus recursive pre-arm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Finally fired buys, on confirmed-locked, sim-passing tokens.&lt;/td&gt;
&lt;td&gt;the strategy functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The result of those buys&lt;/td&gt;
&lt;td&gt;4 tokens passed &lt;strong&gt;everything&lt;/strong&gt;, the buys worked, prices even &lt;strong&gt;rose&lt;/strong&gt;, then every sell reverted.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-100% each&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let me unpack the two findings that actually matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: a rug is not a price crash
&lt;/h2&gt;

&lt;p&gt;The first run was carnage. Twelve trades, twelve losses. I added an exit strategy with a stop-loss and a trailing stop, and it did not help at all. Why?&lt;/p&gt;

&lt;p&gt;Because a &lt;strong&gt;rug pull is not a price movement.&lt;/strong&gt; The deployer does not sell the token down; they call &lt;code&gt;removeLiquidity&lt;/code&gt; and pull the entire pool in one transaction. The price ratio barely moves, so a stop-loss watching price never triggers. One block there is liquidity; the next block the pool is empty and your tokens are unsellable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You cannot out-exit a rug. There is nothing to sell into. The only defense is to never enter, which means proving the liquidity is locked &lt;strong&gt;before&lt;/strong&gt; you buy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I built that. The pair contract is the liquidity token; if enough of its supply sits in a burn address or a known locker, the deployer can't pull it. I made the check recursive: it polls for a window and buys the instant a token proves it is locked.&lt;/p&gt;

&lt;p&gt;That stopped the rugs cold. An entire night with zero losses, because it correctly refused every ruggable launch.&lt;/p&gt;

&lt;p&gt;It also revealed Finding 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: the survivors are engineered to be traps
&lt;/h2&gt;

&lt;p&gt;When the bot finally bought tokens that had locked their liquidity and passed the honeypot simulation, four of them did something I will never forget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;14:05  BUY VIT      (passed LP lock + honeypot sim, real buy succeeded)
14:08  VIT +16.7%   (it went UP, sellable a moment ago)
14:08  SELL reverts. retry. reverts. x5. unsellable. -100%.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same for three others, all dressed as fake stablecoins (&lt;code&gt;USDM&lt;/code&gt;, &lt;code&gt;PUSD&lt;/code&gt;, &lt;code&gt;USDTR&lt;/code&gt;) to look legitimate.&lt;/p&gt;

&lt;p&gt;These are &lt;strong&gt;switchable honeypots&lt;/strong&gt;, and they are beautiful, evil engineering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The token is genuinely sellable at the instant the simulator checks it, so it passes. The bot buys. The deployer lets bots accumulate for a few minutes, then flips a switch (&lt;code&gt;setMaxSell(0)&lt;/code&gt;, a blacklist, a pause) and freezes every buyer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A honeypot simulation is a &lt;strong&gt;point-in-time&lt;/strong&gt; check. It cannot see a future state change. There is no pre-buy filter for "the deployer will disable sells three minutes from now," because that information does not exist when you have to decide.&lt;/p&gt;




&lt;h1&gt;
  
  
  The structural conclusion
&lt;/h1&gt;

&lt;p&gt;After all of it, BSC launches sort into exactly three buckets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token type&lt;/th&gt;
&lt;th&gt;My defense&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no liquidity lock&lt;/td&gt;
&lt;td&gt;LP check&lt;/td&gt;
&lt;td&gt;skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;static honeypot&lt;/td&gt;
&lt;td&gt;sell simulation&lt;/td&gt;
&lt;td&gt;skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;switchable honeypot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none possible&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And here is the part that makes it unwinnable, not just hard:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The tokens that survive your funnel are precisely the ones built to survive your funnel. Every filter you add does not reduce your losses, it &lt;strong&gt;selects&lt;/strong&gt; for a more sophisticated adversary. You are not fighting bad luck. You are fighting someone who adapts to your exact defenses and keeps the timing advantage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Total cost of learning this, for real, on-chain, with data I can point to: about &lt;strong&gt;$10&lt;/strong&gt;. The entire "buy my sniper bot" industry is built on never telling you this.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I actually took away
&lt;/h1&gt;

&lt;p&gt;The losses were tiny. The lessons were not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On engineering.&lt;/strong&gt; This is the kind of project I value most: the one where I did not already know the domain (MEV, AMM math, &lt;code&gt;eth_simulateV1&lt;/code&gt;, honeypot mechanics) and had to decompose it fast enough to build something real. The bot is genuinely good: a clean staged pipeline, honest AMM-accurate accounting, a full live-execution engine, chain-agnostic by config. None of that was the hard part.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Engineering is rarely about already knowing the technology. It is about decomposing an unfamiliar, adversarial system fast enough to get a true answer out of it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;On markets.&lt;/strong&gt; Both ends of what I tried are losing games for retail, for opposite reasons. Efficient markets (arbitrage) have no edge, taken by faster, richer players. Inefficient markets (new launches) have an edge that exists because the place is a minefield. The scams live exactly where the only opportunity is. There is no permissionless venue that is both active and clean. That is not a config you can find, it is the structure of the thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On honesty.&lt;/strong&gt; I could have stopped at the green paper-trading numbers and written a very different, much more flattering post. The whole point was to not do that. I built an instrument precise enough to tell me the truth, and then I believed it.&lt;/p&gt;

&lt;p&gt;A bot that loses $10 and hands you a correct, hard-won conclusion is worth more than one that shows you a fake profit chart and quietly drains your wallet.&lt;/p&gt;

&lt;p&gt;I am calling this project finished. It did exactly what I built it to do: it answered the question honestly.&lt;/p&gt;

&lt;p&gt;The code is a complete, working liquidity sniper. The result is a clear "no." Both of those are, to me, a success.&lt;/p&gt;




&lt;p&gt;The full source is open (GPL-3.0): &lt;strong&gt;&lt;a href="https://github.com/jeyem/lqsniper" rel="noopener noreferrer"&gt;github.com/jeyem/lqsniper&lt;/a&gt;&lt;/strong&gt;. Read the field report in the README, and please remember the disclaimer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://e-mahmoudi.me/blog/i-built-a-liquidity-sniper-bot-for-bnb-chain-and-it-proved-a-losing-game/" rel="noopener noreferrer"&gt;e-mahmoudi.me&lt;/a&gt;. I write about blockchain, backend, and software architecture — more at &lt;a href="https://e-mahmoudi.me/" rel="noopener noreferrer"&gt;e-mahmoudi.me&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>blockchain</category>
      <category>bsc</category>
      <category>defi</category>
    </item>
  </channel>
</rss>
