<?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: Victor Eduardo Oliveira</title>
    <description>The latest articles on DEV Community by Victor Eduardo Oliveira (@oeduardoal).</description>
    <link>https://dev.to/oeduardoal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F420081%2F477af13a-3ef3-479c-8600-ca374aa12e2a.JPG</url>
      <title>DEV Community: Victor Eduardo Oliveira</title>
      <link>https://dev.to/oeduardoal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oeduardoal"/>
    <language>en</language>
    <item>
      <title>Someone Backdoored axios on npm. Here is How to Check if You Were Hit</title>
      <dc:creator>Victor Eduardo Oliveira</dc:creator>
      <pubDate>Tue, 31 Mar 2026 11:32:58 +0000</pubDate>
      <link>https://dev.to/oeduardoal/someone-backdoored-axios-on-npm-here-is-how-to-check-if-you-were-hit-2pin</link>
      <guid>https://dev.to/oeduardoal/someone-backdoored-axios-on-npm-here-is-how-to-check-if-you-were-hit-2pin</guid>
      <description>&lt;p&gt;On March 31, 2026, two malicious versions of &lt;code&gt;axios&lt;/code&gt; were published to npm: &lt;code&gt;axios@1.14.1&lt;/code&gt; and &lt;code&gt;axios@0.30.4&lt;/code&gt;. Both were live for roughly three hours before npm pulled them down. During that window, anyone who ran &lt;code&gt;npm install axios&lt;/code&gt; could have had a Remote Access Trojan (RAT) dropped silently on their machine or CI runner, with no errors and no warnings.&lt;/p&gt;

&lt;p&gt;This post breaks down what happened, how the attack worked, and the exact commands to check if you were affected.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;The attacker compromised the npm account of the primary axios maintainer. Using stolen credentials, they published two new releases across both the 1.x and 0.x branches within 39 minutes of each other. The account's registered email was quietly changed to an attacker-controlled ProtonMail address before the releases went out.&lt;/p&gt;

&lt;p&gt;Here is what makes this attack stand out: &lt;strong&gt;there is zero malicious code inside axios itself.&lt;/strong&gt; Both releases simply added one new runtime dependency to &lt;code&gt;package.json&lt;/code&gt;: a package called &lt;code&gt;plain-crypto-js@4.2.1&lt;/code&gt;, which had never appeared in any legitimate axios version.&lt;/p&gt;

&lt;p&gt;When you ran &lt;code&gt;npm install&lt;/code&gt;, npm resolved and installed that dependency automatically, which triggered its &lt;code&gt;postinstall&lt;/code&gt; script and silently executed the dropper.&lt;/p&gt;

&lt;h3&gt;
  
  
  The malicious dependency: plain-crypto-js
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;plain-crypto-js@4.2.1&lt;/code&gt; was staged 18 hours before the axios releases, by a separate attacker-controlled account. It masquerades as a clone of the legitimate &lt;code&gt;crypto-js&lt;/code&gt; library, with an identical description and the real author's name in the manifest. Visually, it looks harmless. The only difference from the real package is a single extra field in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grunt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node setup.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;setup.js&lt;/code&gt; is a cross-platform RAT dropper.&lt;/p&gt;

&lt;h3&gt;
  
  
  What setup.js does
&lt;/h3&gt;

&lt;p&gt;The dropper detects your operating system and then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS:&lt;/strong&gt; writes an AppleScript to &lt;code&gt;/tmp&lt;/code&gt;, executes it via &lt;code&gt;nohup osascript&lt;/code&gt;, downloads a binary to &lt;code&gt;/Library/Caches/com.apple.act.mond&lt;/code&gt; (named to blend in with Apple system processes), makes it executable and launches it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux:&lt;/strong&gt; fetches a Python script from the C2 server to &lt;code&gt;/tmp/ld.py&lt;/code&gt; and runs it in the background&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows:&lt;/strong&gt; copies PowerShell to &lt;code&gt;%PROGRAMDATA%\wt.exe&lt;/code&gt; (disguised as Windows Terminal), then uses a VBScript to silently fetch and execute a PowerShell payload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this runs in under two seconds from the moment &lt;code&gt;npm install&lt;/code&gt; starts. The npm install itself exits with code 0 and shows no errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  The anti-forensics layer
&lt;/h3&gt;

&lt;p&gt;After launching the payload, the dropper:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deletes &lt;code&gt;setup.js&lt;/code&gt; from &lt;code&gt;node_modules/plain-crypto-js/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deletes the malicious &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Renames a pre-staged clean stub (&lt;code&gt;package.md&lt;/code&gt;) to &lt;code&gt;package.json&lt;/code&gt;, which reports version &lt;code&gt;4.2.0&lt;/code&gt; with no scripts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After the swap, running &lt;code&gt;npm audit&lt;/code&gt; reveals nothing. Running &lt;code&gt;npm list plain-crypto-js&lt;/code&gt; shows version &lt;code&gt;4.2.0&lt;/code&gt;. The only reliable on-disk evidence is the existence of the &lt;code&gt;node_modules/plain-crypto-js/&lt;/code&gt; directory, since this package was never a dependency of any real axios release.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on the publish metadata
&lt;/h3&gt;

&lt;p&gt;Every legitimate axios 1.x release is published via GitHub Actions with npm's OIDC Trusted Publisher mechanism, meaning the publish is cryptographically tied to a verified workflow. &lt;code&gt;axios@1.14.1&lt;/code&gt; breaks that pattern entirely. There is no corresponding tag or commit in the axios GitHub repository. It was published manually using a stolen npm access token.&lt;/p&gt;




&lt;h2&gt;
  
  
  Am I affected?
&lt;/h2&gt;

&lt;p&gt;The malicious versions were live between approximately &lt;strong&gt;00:21 UTC and 03:15 UTC on March 31, 2026&lt;/strong&gt;. If you did not run &lt;code&gt;npm install&lt;/code&gt; (or a fresh &lt;code&gt;npm ci&lt;/code&gt;) during that window, you were likely not hit.&lt;/p&gt;

&lt;p&gt;Run through these steps to verify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Check for the malicious axios versions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm list axios 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"1&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;14&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;1|0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;30&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;4"&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A1&lt;/span&gt; &lt;span class="s1"&gt;'"axios"'&lt;/span&gt; package-lock.json | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"1&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;14&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;1|0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;30&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No output means you are probably fine. Any output means those versions were installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Check for plain-crypto-js in node_modules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls &lt;/span&gt;node_modules/plain-crypto-js 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"POTENTIALLY AFFECTED"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the directory exists, the dropper ran. Do not rely on the version number inside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Check for RAT artifacts on the system
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /Library/Caches/com.apple.act.mond 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"COMPROMISED"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /tmp/ld.py 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"COMPROMISED"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (cmd.exe)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dir "%PROGRAMDATA%\wt.exe" 2&amp;gt;nul &amp;amp;&amp;amp; echo COMPROMISED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Check for C2 activity in logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"sfrclak.com"&lt;/span&gt; ~/.npm/_logs/ 2&amp;gt;/dev/null
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"sfrclak"&lt;/span&gt; ~/.bash_history ~/.zsh_history 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Check your CI/CD pipelines
&lt;/h3&gt;

&lt;p&gt;Review any &lt;code&gt;npm install&lt;/code&gt; runs in GitHub Actions or other CI systems between 00:20 UTC and 03:15 UTC on March 31. Any pipeline that ran with those axios versions should be treated as compromised, and all secrets injected in that workflow should be rotated immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you got multiple projects running
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;dir &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; ~/projects/&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;/package.json"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;/node_modules/plain-crypto-js"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"DROPPER FOUND"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nv"&gt;axios_hit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A1&lt;/span&gt; &lt;span class="s1"&gt;'"axios"'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;/package-lock.json"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"1&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;14&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;1|0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;30&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;4"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$axios_hit&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="s2"&gt; | MALICIOUS AXIOS VERSION"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"⚠️  &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt; → &lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅  &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  fi
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Remediation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Downgrade to a safe version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1.x users&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;axios@1.14.0

&lt;span class="c"&gt;# 0.x users&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;axios@0.30.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Pin the version in your package.json
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"axios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.14.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resolutions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"axios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.14.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Remove plain-crypto-js and reinstall
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node_modules/plain-crypto-js
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Block the C2 domain (macOS / Linux)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0 sfrclak.com"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; OUTPUT &lt;span class="nt"&gt;-d&lt;/span&gt; 142.11.206.73 &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Add --ignore-scripts to your CI installs going forward
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents any &lt;code&gt;postinstall&lt;/code&gt; hook from running during automated builds, which would have stopped this attack entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you found a RAT artifact
&lt;/h2&gt;

&lt;p&gt;Do not try to clean the machine in place. Rebuild from a known-good state and rotate everything that was accessible during the compromised install: npm tokens, AWS access keys, SSH private keys, cloud credentials, CI/CD secrets, and any &lt;code&gt;.env&lt;/code&gt; values present at install time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Indicators of Compromise
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&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;Malicious packages&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;axios@1.14.1&lt;/code&gt;, &lt;code&gt;axios@0.30.4&lt;/code&gt;, &lt;code&gt;plain-crypto-js@4.2.1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C2 domain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sfrclak.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C2 IP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;142.11.206.73&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C2 port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS artifact&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/Library/Caches/com.apple.act.mond&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux artifact&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tmp/ld.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows artifact&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%PROGRAMDATA%\wt.exe&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safe 1.x version&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;axios@1.14.0&lt;/code&gt; (shasum &lt;code&gt;7c29f4cf2ea91ef05018d5aa5399bf23ed3120eb&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;This attack did not require any vulnerability in axios itself. It exploited the trust developers place in a well-known package name and a familiar maintainer account. The entire weapon was a single extra line in &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A few habits that would have reduced exposure here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;npm ci --ignore-scripts&lt;/code&gt; in CI pipelines as a standing policy&lt;/li&gt;
&lt;li&gt;Use npm's &lt;a href="https://docs.npmjs.com/generating-provenance-statements" rel="noopener noreferrer"&gt;Trusted Publisher&lt;/a&gt; provenance statements to verify releases were published from the expected CI workflow&lt;/li&gt;
&lt;li&gt;Watch for new dependencies appearing in transitive installs that were not there before&lt;/li&gt;
&lt;li&gt;Pin your lockfile and review &lt;code&gt;package-lock.json&lt;/code&gt; diffs in pull requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full technical breakdown, including the decoded dropper, process tree, and file swap events captured at runtime, is in the &lt;a href="https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan" rel="noopener noreferrer"&gt;StepSecurity report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Stay safe.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>news</category>
      <category>npm</category>
      <category>security</category>
    </item>
    <item>
      <title>Understanding blockchain RPCs from scratch</title>
      <dc:creator>Victor Eduardo Oliveira</dc:creator>
      <pubDate>Sat, 28 Mar 2026 18:09:00 +0000</pubDate>
      <link>https://dev.to/oeduardoal/understanding-blockchain-rpcs-from-scratch-lgl</link>
      <guid>https://dev.to/oeduardoal/understanding-blockchain-rpcs-from-scratch-lgl</guid>
      <description>&lt;p&gt;A few days ago I deployed &lt;a href="https://gasfee.oeduardoal.dev" rel="noopener noreferrer"&gt;gasfee.oeduardoal.dev&lt;/a&gt;, a small tool that shows real-time transaction fees across 7 different blockchains.&lt;/p&gt;

&lt;p&gt;The UI is simple, but building it forced me to understand something I had always glossed over: how does your code actually talk to a blockchain?&lt;/p&gt;

&lt;p&gt;If you are a backend dev who has never touched Web3, this one is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a blockchain, from a backend perspective?
&lt;/h2&gt;

&lt;p&gt;Think of a blockchain as a database you do not own. It is a distributed ledger running across thousands of nodes worldwide. There is no single server. It is a network of computers that all agree on the same state.&lt;/p&gt;

&lt;p&gt;As a backend dev, your instinct is: "okay, so how do I query it?" That is where RPCs come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an RPC?
&lt;/h2&gt;

&lt;p&gt;RPC stands for Remote Procedure Call. In the blockchain world, it is the interface between your app and a node on the network. You do not run the node, you just call it.&lt;/p&gt;

&lt;p&gt;It works over HTTP and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://eth.llamarpc.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Content-Type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;application/json&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eth_gasPrice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regular HTTP POST. The node responds with the current gas price in wei. Libraries like &lt;code&gt;viem&lt;/code&gt; or &lt;code&gt;ethers.js&lt;/code&gt; wrap this for you, but under the hood it is just JSON over HTTP.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RPC endpoints are to blockchains what REST APIs are to web services. You are calling a server you do not control, and it returns data from the network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is gas?
&lt;/h2&gt;

&lt;p&gt;Every operation on an EVM blockchain costs gas. Think of it as a fee you pay to compensate the validators who process and store your transaction.&lt;/p&gt;

&lt;p&gt;The price fluctuates with demand, just like Uber surge pricing. When the network is busy, fees go up. When it is quiet, they drop.&lt;/p&gt;

&lt;p&gt;Gas is measured in &lt;strong&gt;gwei&lt;/strong&gt;, which is just a small unit of ETH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 ETH = 1,000,000,000 gwei
1 gwei = 0.000000001 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A standard ETH transfer costs 21,000 units of gas. So if the gas price is 10 gwei, your transaction costs &lt;code&gt;21000 × 10 gwei = 0.00021 ETH&lt;/code&gt;, which you then convert to USD using the current ETH price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 7 chains?
&lt;/h2&gt;

&lt;p&gt;All 7 chains in the tracker are &lt;strong&gt;EVM-compatible&lt;/strong&gt;, meaning they speak the same RPC protocol as Ethereum. The same method names, the same response format. The only difference is the endpoint URL and the native token price.&lt;/p&gt;

&lt;p&gt;This is what makes building cross-chain tools surprisingly approachable. Ethereum, Base, Polygon, Arbitrum, Optimism all use the same call, just a different URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ethereum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://eth.llamarpc.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Base&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="na"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://base.llamarpc.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Polygon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://polygon.llamarpc.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;chains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchGasPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Promise.allSettled&lt;/code&gt; instead of &lt;code&gt;Promise.all&lt;/code&gt;, so a single chain being down does not break the whole UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Blockchain is not magic. It is a distributed system with an HTTP interface. If you know how to call an API, you already know most of what you need to start exploring Web3.&lt;/p&gt;




&lt;p&gt;The tracker is live at &lt;a href="https://gasfee.oeduardoal.dev" rel="noopener noreferrer"&gt;gasfee.oeduardoal.dev&lt;/a&gt;. Questions? Drop a comment, happy to go deeper on any of this.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>beginners</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Where Is the Real Risk in Cross-Chain Bridges?</title>
      <dc:creator>Victor Eduardo Oliveira</dc:creator>
      <pubDate>Sat, 28 Mar 2026 15:28:17 +0000</pubDate>
      <link>https://dev.to/oeduardoal/where-is-the-real-risk-in-cross-chain-bridges-12j8</link>
      <guid>https://dev.to/oeduardoal/where-is-the-real-risk-in-cross-chain-bridges-12j8</guid>
      <description>&lt;p&gt;Every bridge has risk. The question is where.&lt;/p&gt;

&lt;p&gt;When people talk about bridge security, the conversation usually jumps to "bridges got hacked." And that's true, some did, spectacularly. But treating all bridges as equally dangerous misses the point. The risk profile depends entirely on the model: what's being trusted, who controls it, and what happens when something breaks.&lt;/p&gt;

&lt;p&gt;I've spent enough time working with cross-chain stablecoins to develop a mental model for this. Here's how I think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk in Lock &amp;amp; Mint
&lt;/h2&gt;

&lt;p&gt;Canonical bridges (the lock &amp;amp; mint model) concentrate risk in one place: the bridge contract.&lt;/p&gt;

&lt;p&gt;When you lock 100 USDC in a bridge contract on Ethereum to mint 100 USDC.e on Arbitrum, that contract is now holding your USDC. Scale that up across thousands of users and you get a contract sitting on hundreds of millions of dollars. That's a honeypot.&lt;/p&gt;

&lt;p&gt;The attack surface is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart contract bugs.&lt;/strong&gt; The bridge contract itself might have vulnerabilities. The Wormhole exploit ($320M, Feb 2022) happened because an attacker bypassed signature verification and minted tokens without an actual deposit. The Ronin bridge ($625M, March 2022) was compromised through validator key theft. Five out of nine validators were controlled by the attacker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admin key compromise.&lt;/strong&gt; Most bridge contracts have upgrade mechanisms or admin functions. If those keys are compromised (phishing, operational security failures, insider action), the attacker can drain the locked funds or modify the contract logic. Multi-sig setups reduce this risk but don't eliminate it. The number of signers and their operational security matter enormously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upgrade risks.&lt;/strong&gt; Upgradeable contracts mean the code you audited yesterday might not be the code running today. A malicious or compromised upgrade can change the rules silently.&lt;/p&gt;

&lt;p&gt;The core issue with lock &amp;amp; mint is structural: all value is concentrated in one contract. The more successful the bridge, the bigger the target. And the wrapped tokens on the other side are only worth anything as long as that contract is intact and solvent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk in CCTP
&lt;/h2&gt;

&lt;p&gt;CCTP shifts the trust model. Instead of trusting a contract holding pooled funds, you're trusting Circle's attestation infrastructure.&lt;/p&gt;

&lt;p&gt;Here's the flow: USDC is burned on the source chain, Circle's attestation service (Iris) observes the burn and signs a message, and that signed message authorizes minting on the destination chain. There's no pool of locked tokens to steal.&lt;/p&gt;

&lt;p&gt;The risk profile is different:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attestation service availability.&lt;/strong&gt; If Iris goes down, transfers stop. No attestation, no mint. This is a liveness risk, not a safety risk. Your funds aren't lost, they're just stuck until the service recovers. But for applications that depend on timely transfers, downtime is a real problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Circle's authority.&lt;/strong&gt; Circle can pause the protocol. They can blacklist addresses. They can refuse to attest. This is by design. It's how they comply with regulations and prevent illicit use. But it means your ability to move USDC cross-chain depends on Circle's continued willingness to process your transfer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Centralization trade-off.&lt;/strong&gt; CCTP is operationally centralized. Circle runs Iris. There's no decentralized fallback if Circle decides to stop, gets shut down by a regulator, or changes their terms. For USDC specifically, this isn't a new trust assumption. You're already trusting Circle to honor the reserves. But it's worth being explicit about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No honeypot, but single point of failure.&lt;/strong&gt; The good news: there's no giant pool of locked funds to hack. The bad news: the entire system depends on one company's infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk in LayerZero / USDT0
&lt;/h2&gt;

&lt;p&gt;USDT0 uses LayerZero's messaging layer, which introduces a different security architecture.&lt;/p&gt;

&lt;p&gt;On Ethereum, USDT is locked in the OFT Adapter. That's a lock &amp;amp; mint step, and the adapter contract carries the same honeypot risk as any canonical bridge. Between other chains, USDT0 uses burn &amp;amp; mint, so there's no pooled fund risk there.&lt;/p&gt;

&lt;p&gt;The cross-chain messaging security comes from DVNs (Decentralized Verifier Networks). Here's how the trust model works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configurable security.&lt;/strong&gt; The token deployer (in this case, Tether) chooses which DVNs must verify each cross-chain message. This could be a single verifier, a required set of multiple verifiers, or a threshold (e.g., 2 out of 3). The security depends on this configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DVN collusion.&lt;/strong&gt; If the required DVNs collude or are compromised, they could forge a message and authorize a mint on the destination chain without a corresponding burn on the source. Think of it like validator collusion in a proof-of-stake system. The more independent DVNs required, the harder this attack becomes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relayer independence.&lt;/strong&gt; Messages in LayerZero are delivered by relayers, which are separate from verifiers. The security doesn't depend on the relayer being honest (anyone can relay a message). But the relayer being operational matters for liveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issuer control.&lt;/strong&gt; Tether retains control over the OFT contracts. They can pause, upgrade, or blacklist. Similar to Circle with CCTP, the issuer has ultimate authority over their token. The difference is the transport layer: LayerZero's DVN model vs Circle's proprietary Iris service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can Actually Break
&lt;/h2&gt;

&lt;p&gt;It's useful to distinguish two categories of failure: &lt;strong&gt;safety failures&lt;/strong&gt; (tokens minted without a valid lock or burn — a double-spend) and &lt;strong&gt;liveness failures&lt;/strong&gt; (transfers stuck because an attestation service or verifier is down). Lock &amp;amp; mint concentrates safety risk in the bridge contract. CCTP and OFT largely trade safety risk for liveness risk — there's less to steal, but more that can stall.&lt;/p&gt;

&lt;p&gt;Across all three models, the failure modes fall into a few categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source chain reorgs.&lt;/strong&gt; If the source chain reorganizes after a bridge transaction is processed, you can end up with minted tokens on the destination chain without a valid burn or lock on the source. This is why bridges wait for block confirmations before processing, but the wait time is always a trade-off between speed and safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Message delivery failures.&lt;/strong&gt; In CCTP, if Iris doesn't produce an attestation. In LayerZero, if DVNs don't verify or relayers don't deliver. These are liveness failures. Funds aren't lost, but they're stuck. Applications need to handle this gracefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract upgrades gone wrong.&lt;/strong&gt; All three models involve upgradeable contracts. A bug in an upgrade, a compromised deployer key, or a malicious governance proposal can change the rules. Timelocks and multi-sig controls help, but they shift the question to "who holds those keys and how secure are they?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regulatory action.&lt;/strong&gt; Both CCTP and USDT0 have centralized issuers. A regulatory order to freeze assets, pause operations, or delist a chain is a real possibility. Lock &amp;amp; mint bridges with decentralized governance are more resistant to this, but they carry the other risks mentioned above.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Think About It
&lt;/h2&gt;

&lt;p&gt;I don't rank these models from safe to unsafe. I think about them in terms of where the risk sits.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pooled fund risk&lt;/th&gt;
&lt;th&gt;Centralization risk&lt;/th&gt;
&lt;th&gt;Messaging risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lock &amp;amp; Mint&lt;/td&gt;
&lt;td&gt;High (bridge contract)&lt;/td&gt;
&lt;td&gt;Varies (depends on governance)&lt;/td&gt;
&lt;td&gt;Low (usually direct L1 verification)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CCTP&lt;/td&gt;
&lt;td&gt;None (no locked pool)&lt;/td&gt;
&lt;td&gt;High (Circle controls everything)&lt;/td&gt;
&lt;td&gt;Medium (depends on Iris uptime)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;USDT0/OFT&lt;/td&gt;
&lt;td&gt;Ethereum adapter only&lt;/td&gt;
&lt;td&gt;High (Tether controls token)&lt;/td&gt;
&lt;td&gt;Medium (depends on DVN config)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lock &amp;amp; mint puts risk in the contract. CCTP puts risk in Circle. LayerZero puts risk in the DVN configuration and the Ethereum adapter.&lt;/p&gt;

&lt;p&gt;None of these are zero-risk. The question is which risks you're comfortable with for your use case. If you're building a protocol that processes millions in stablecoin transfers, you need to understand these trade-offs at a mechanical level, not just "bridges are risky."&lt;/p&gt;

&lt;p&gt;There's no perfect model, only trade-offs. The best you can do is understand exactly what you're trusting and make that decision consciously.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the final post in a 3-part series on cross-chain stablecoins. See also: &lt;a href="https://dev.to/oeduardoal/lock-mint-vs-burn-mint-what-i-learned-about-cross-chain-stablecoins-1p7l"&gt;Post 1: Lock &amp;amp; Mint vs Burn &amp;amp; Mint&lt;/a&gt; and &lt;a href="https://dev.to/oeduardoal/liquidity-fragmentation-is-more-expensive-than-it-looks-42ba"&gt;Post 2: Liquidity Fragmentation&lt;/a&gt;. Official protocol docs: &lt;a href="https://developers.circle.com/cctp" rel="noopener noreferrer"&gt;CCTP&lt;/a&gt; · &lt;a href="https://docs.usdt0.to/" rel="noopener noreferrer"&gt;USDT0&lt;/a&gt; · &lt;a href="https://docs.layerzero.network/v2/developers/evm/oft/quickstart" rel="noopener noreferrer"&gt;LayerZero OFT&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Liquidity Fragmentation Is More Expensive Than It Looks</title>
      <dc:creator>Victor Eduardo Oliveira</dc:creator>
      <pubDate>Sat, 28 Mar 2026 15:27:41 +0000</pubDate>
      <link>https://dev.to/oeduardoal/liquidity-fragmentation-is-more-expensive-than-it-looks-42ba</link>
      <guid>https://dev.to/oeduardoal/liquidity-fragmentation-is-more-expensive-than-it-looks-42ba</guid>
      <description>&lt;p&gt;Go to any DEX on Arbitrum and search for USDC. You'll find at least two: USDC and USDC.e. Same dollar. Same peg. Different contracts.&lt;/p&gt;

&lt;p&gt;Now try to swap USDC.e for something. You might get a worse price than if you had native USDC, because the liquidity pool for USDC.e is thinner. Or the router sends you through an extra hop (USDC.e → USDC → your target token) and you eat the slippage twice.&lt;/p&gt;

&lt;p&gt;This is liquidity fragmentation. And it costs more than most people realize.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: One Dollar, Five Tokens
&lt;/h2&gt;

&lt;p&gt;When multiple bridges connect the same token to a chain, each bridge creates its own wrapped version. On Arbitrum alone, you've had USDC.e (Arbitrum's canonical bridge), and native USDC (issued by Circle). On other chains, you might see bridged versions from Wormhole, Multichain (before it collapsed), Synapse, and others, each with a different contract address.&lt;/p&gt;

&lt;p&gt;They're all supposed to be worth $1. But DeFi doesn't care about your intentions. It cares about contract addresses.&lt;/p&gt;

&lt;p&gt;Each version needs its own liquidity pool. Each pool competes for the same TVL. The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thinner pools everywhere.&lt;/strong&gt; Instead of $100M in one USDC pool, you get $60M in USDC and $40M split across USDC.e, USDC.wh, and others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worse prices for users.&lt;/strong&gt; Thinner pools mean more slippage on larger trades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More complex routing.&lt;/strong&gt; DEX aggregators have to route through multiple hops, each one adding gas costs and slippage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arbitrage overhead.&lt;/strong&gt; Someone has to keep these versions at parity. That's capital being deployed just to maintain a peg between tokens that represent the same thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Say you're building a lending protocol on an L2. You need to decide which USDC to accept as collateral. Native USDC? USDC.e? Both?&lt;/p&gt;

&lt;p&gt;If you accept both, your collateral pool has two tokens and you need oracle prices for each. If you only accept one, users holding the other version have to swap first. Friction, gas, maybe slippage.&lt;/p&gt;

&lt;p&gt;Now multiply this by every protocol on the chain. Every DEX, lending market, yield vault, and payment app makes the same decision. The ecosystem ends up with a patchwork of liquidity that doesn't compose cleanly.&lt;/p&gt;

&lt;p&gt;I've seen this firsthand. Integrating with protocols that only accept native USDC means users bridging through the canonical bridge have an extra step. Some don't realize USDC.e isn't "the" USDC. Support tickets follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Burn &amp;amp; Mint Fixes This
&lt;/h2&gt;

&lt;p&gt;The fundamental fix is simple: if there's only one version of the token on each chain, fragmentation disappears.&lt;/p&gt;

&lt;p&gt;That's exactly what CCTP does for USDC. When you transfer USDC from Ethereum to Arbitrum via CCTP, you get native USDC on Arbitrum. The same contract that Circle deployed. Not a wrapped version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lock &amp;amp; Mint world:
  Ethereum USDC ──bridge A──► Chain X: USDC.e
  Ethereum USDC ──bridge B──► Chain X: USDC.b
  Ethereum USDC ──bridge C──► Chain X: USDC.c
  Result: 3 pools, fragmented liquidity

Burn &amp;amp; Mint world:
  Ethereum USDC ──CCTP──► Chain X: USDC (native)
  Result: 1 pool, unified liquidity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every protocol on Chain X integrates with one USDC contract. One liquidity pool. One oracle feed. Users don't have to think about which version they're holding.&lt;/p&gt;

&lt;p&gt;USDT0 does the same thing for USDT through the OFT model. Wherever USDT0 is deployed, it's the canonical version. There's no USDT.e competing for liquidity. Just USDT0. If you bridge from Arbitrum to Optimism, you burn USDT0 on Arbitrum and mint USDT0 on Optimism. Same token, same contract standard, unified liquidity on every chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Costs You Don't See
&lt;/h2&gt;

&lt;p&gt;Fragmentation has costs that don't show up on a balance sheet but erode the system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capital inefficiency.&lt;/strong&gt; Liquidity providers have to spread capital across multiple versions of the same token. This means each pool is shallower, which means worse execution for traders, which means LPs earn less. A negative feedback loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration burden.&lt;/strong&gt; Every protocol team has to decide which token versions to support. Every version they add is another contract to audit, another oracle to configure, another edge case to handle. This is real engineering time spent on a problem that shouldn't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User confusion.&lt;/strong&gt; Most users don't understand why their USDC.e can't be deposited into a vault that says "USDC." They see the dollar sign, they see the peg, and they expect it to work. When it doesn't, the experience feels broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arbitrage as a tax.&lt;/strong&gt; Keeping wrapped versions at peg requires arbitrageurs. Their profit comes from price discrepancies, which means someone else is getting a worse price. The tighter the peg needs to be, the more capital is tied up in this unproductive work.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Not Fully Solved
&lt;/h2&gt;

&lt;p&gt;Burn &amp;amp; mint models reduce fragmentation significantly, but they don't eliminate all the complexity.&lt;/p&gt;

&lt;p&gt;CCTP only works for USDC. If you need to move ETH, WBTC, or any other token, you're still using lock &amp;amp; mint bridges and dealing with wrapped versions. The fragmentation problem is solved for one token, not the ecosystem.&lt;/p&gt;

&lt;p&gt;USDT0 solves it for USDT but requires chains to adopt the OFT-based version. Chains that already have USDT through other bridges may end up with both USDT and USDT0 coexisting, which is its own kind of fragmentation.&lt;/p&gt;

&lt;p&gt;And both models introduce different centralization trade-offs. CCTP centralizes control with Circle. USDT0 centralizes with Tether and depends on LayerZero's infrastructure. Whether that's better or worse than fragmentation depends on what you value.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Take
&lt;/h2&gt;

&lt;p&gt;Fragmentation is a hidden tax on the entire cross-chain ecosystem. It makes everything slightly worse (prices, UX, developer experience, capital efficiency) in ways that are easy to overlook because no single instance looks catastrophic.&lt;/p&gt;

&lt;p&gt;Burn &amp;amp; mint models are a real improvement. Having one canonical token per chain is strictly better than having five wrapped versions competing for liquidity. But it comes with centralization costs, and it only works when the issuer is willing and able to operate the burn &amp;amp; mint infrastructure.&lt;/p&gt;

&lt;p&gt;For now, the practical advice is: if you're building on a chain where both USDC.e and native USDC exist, default to native USDC. Encourage users to bridge via CCTP. Minimize your exposure to wrapped versions when you can. The fewer token versions in your system, the simpler everything becomes.&lt;/p&gt;

&lt;p&gt;Next post: &lt;a href="https://dev.to/oeduardoal/where-is-the-real-risk-in-cross-chain-bridges-12j8"&gt;Where Is the Real Risk in Cross-Chain Bridges?&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Lock &amp; Mint vs Burn &amp; Mint: What I Learned About Cross-Chain Stablecoins</title>
      <dc:creator>Victor Eduardo Oliveira</dc:creator>
      <pubDate>Sat, 28 Mar 2026 15:27:18 +0000</pubDate>
      <link>https://dev.to/oeduardoal/lock-mint-vs-burn-mint-what-i-learned-about-cross-chain-stablecoins-1p7l</link>
      <guid>https://dev.to/oeduardoal/lock-mint-vs-burn-mint-what-i-learned-about-cross-chain-stablecoins-1p7l</guid>
      <description>&lt;p&gt;Very recently at &lt;a href="https://lootrush.com/" rel="noopener noreferrer"&gt;LootRush&lt;/a&gt; where I work as Senior Software Engineer, I had to implement a bridge tool to help our users to transfer stablecoins to different networks, what we call a bridge and I used to think all bridges worked the same way. You send tokens on one chain, they show up on another. Done.&lt;/p&gt;

&lt;p&gt;Then, after working with stablecoins across multiple chains, I realized the mechanics underneath are very different. Those differences have real consequences for liquidity, risk, and what token you actually end up holding.&lt;/p&gt;

&lt;p&gt;Here's what I've learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canonical Bridges: Lock &amp;amp; Mint
&lt;/h2&gt;

&lt;p&gt;This is the oldest model. Most L2 bridges work this way.&lt;/p&gt;

&lt;p&gt;You deposit USDC into a bridge contract on Ethereum. The contract locks your tokens. On the destination chain (say, Arbitrum), the bridge mints a wrapped version: USDC.e.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethereum                          Arbitrum
┌──────────────┐                  ┌──────────────┐
│  User sends  │                  │  Bridge mints│
│  100 USDC    │───────────────►  │  100 USDC.e  │
│              │   lock &amp;amp; mint    │              │
└──────────────┘                  └──────────────┘
       │                                 │
       ▼                                 ▼
  Bridge contract                  Wrapped token
  holds 100 USDC                   (not the real thing)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The USDC.e you receive isn't USDC. It's an IOU. It's backed by the USDC sitting in the bridge contract on Ethereum, but it's a different token with a different contract address. DEXs need separate pools for it. Protocols may or may not accept it.&lt;/p&gt;

&lt;p&gt;To go back, you burn the USDC.e on Arbitrum and the bridge releases the original USDC on Ethereum.&lt;/p&gt;

&lt;p&gt;This model works, but it has a structural problem: the bridge contract becomes a giant pool of locked funds. That's a honeypot. And the token you hold on the destination chain is only as good as the bridge that issued it.&lt;/p&gt;

&lt;h2&gt;
  
  
  CCTP: Burn &amp;amp; Mint
&lt;/h2&gt;

&lt;p&gt;Circle's Cross-Chain Transfer Protocol takes a different approach. Instead of locking tokens on one side and minting a wrapped version on the other, CCTP burns USDC on the source chain and mints native USDC on the destination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethereum                          Arbitrum
┌──────────────┐                  ┌──────────────┐
│  100 USDC    │                  │  100 USDC    │
│  burned      │───────────────►  │  minted      │
│              │   burn &amp;amp; mint    │  (native)    │
└──────────────┘                  └──────────────┘
       │                                 │
       ▼                                 ▼
  Supply decreases                 Supply increases
  on Ethereum                      on Arbitrum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your app calls &lt;code&gt;depositForBurn&lt;/code&gt; on the source chain. USDC is burned.&lt;/li&gt;
&lt;li&gt;Circle's attestation service (called Iris) observes the burn event and signs an attestation.&lt;/li&gt;
&lt;li&gt;The attestation is submitted on the destination chain, and &lt;code&gt;receiveMessage&lt;/code&gt; mints new USDC.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No wrapped tokens. No liquidity pools. The USDC you receive on Arbitrum is the same native USDC that Circle issues on that chain. One contract address, one token, full composability with every protocol on that chain.&lt;/p&gt;

&lt;p&gt;The trade-off is clear: you're trusting Circle. Iris is the bottleneck. Circle can pause the protocol, blacklist addresses, or delay attestations. But for USDC specifically, you're already trusting Circle with the reserves, so this isn't a new trust assumption. It's the same one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Circle now positions &lt;a href="https://www.circle.com/blog/cctp-version-updates" rel="noopener noreferrer"&gt;CCTP V2&lt;/a&gt; as the canonical version (launched March 2025), adding Fast Transfers and Hooks. The core mechanic — burn, attest, mint — is unchanged. Official docs: &lt;a href="https://developers.circle.com/cctp" rel="noopener noreferrer"&gt;developers.circle.com/cctp&lt;/a&gt;. Runnable simulation: &lt;a href="https://github.com/oeduardoal/blockchain-cross-chain-liquidity-examples/blob/main/examples/cctp/simulate-burn-mint.ts" rel="noopener noreferrer"&gt;&lt;code&gt;examples/cctp/simulate-burn-mint.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  USDT0: The OFT Model
&lt;/h2&gt;

&lt;p&gt;Tether went a different route with USDT0, built on LayerZero's Omnichain Fungible Token (OFT) standard.&lt;/p&gt;

&lt;p&gt;It's a hybrid. On Ethereum, USDT is locked in an OFT Adapter contract, similar to a canonical bridge. But on every other chain, USDT0 is the token, and transfers between non-Ethereum chains use burn &amp;amp; mint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethereum (source of truth)
┌──────────────────────┐
│  USDT locked in      │
│  OFT Adapter         │
└──────────┬───────────┘
           │ lock
           ▼
    ┌──────────────┐         ┌──────────────┐
    │  Arbitrum    │◄───────►│  Optimism    │
    │  USDT0       │  burn   │  USDT0       │
    │  (minted)    │  &amp;amp; mint │  (minted)    │
    └──────────────┘         └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So Ethereum → Arbitrum is lock &amp;amp; mint. But Arbitrum → Optimism is burn &amp;amp; mint. No need to go back through Ethereum.&lt;/p&gt;

&lt;p&gt;The cross-chain messaging goes through LayerZero's network, which uses Decentralized Verifier Networks (DVNs) to validate messages. Unlike CCTP's single attestation service, it's a configurable security stack where the token deployer chooses which verifiers to require.&lt;/p&gt;

&lt;p&gt;USDT0 gives Tether a single canonical token across all chains where it's deployed. No USDT.e, no USDT.arb. Just USDT0 everywhere. The supply is always backed 1:1 by USDT locked in the Ethereum adapter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Official docs: &lt;a href="https://docs.usdt0.to/" rel="noopener noreferrer"&gt;docs.usdt0.to&lt;/a&gt; · &lt;a href="https://docs.usdt0.to/technical-documentation/security" rel="noopener noreferrer"&gt;Security model&lt;/a&gt;. OFT standard: &lt;a href="https://docs.layerzero.network/v2/developers/evm/oft/quickstart" rel="noopener noreferrer"&gt;LayerZero OFT quickstart&lt;/a&gt;. Runnable simulation: &lt;a href="https://github.com/oeduardoal/blockchain-cross-chain-liquidity-examples/blob/main/examples/usdt0-oft/simulate-oft-transfer.ts" rel="noopener noreferrer"&gt;&lt;code&gt;examples/usdt0-oft/simulate-oft-transfer.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How They Compare
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lock &amp;amp; Mint (Canonical Bridges)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates wrapped tokens (USDC.e, WETH, etc.)&lt;/li&gt;
&lt;li&gt;Bridge contract holds all locked funds&lt;/li&gt;
&lt;li&gt;Each bridge creates its own version of the token&lt;/li&gt;
&lt;li&gt;Risk is concentrated in the bridge contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Burn &amp;amp; Mint (CCTP)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No wrapped tokens, native USDC on every chain&lt;/li&gt;
&lt;li&gt;Supply is managed globally by Circle&lt;/li&gt;
&lt;li&gt;Trust is in Circle's attestation service&lt;/li&gt;
&lt;li&gt;Only works for USDC (Circle-controlled tokens)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;OFT / USDT0 (LayerZero)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock on Ethereum, burn &amp;amp; mint everywhere else&lt;/li&gt;
&lt;li&gt;Single canonical token across all chains&lt;/li&gt;
&lt;li&gt;Cross-chain security depends on DVN configuration&lt;/li&gt;
&lt;li&gt;Issuer (Tether) retains full control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;If you're building anything that touches stablecoins across chains, the bridge model determines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What token your users actually hold.&lt;/strong&gt; Native vs wrapped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How deep the liquidity is.&lt;/strong&gt; One pool vs fragmented across versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where the risk sits.&lt;/strong&gt; Bridge contract, attestation service, or messaging layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether protocols on the destination chain accept it.&lt;/strong&gt; Not every protocol lists USDC.e.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used to think the bridge was just plumbing. It's not. It's architecture. The model you use shapes the token economics, the risk profile, and the user experience on the other side.&lt;/p&gt;

&lt;p&gt;Next post: &lt;a href="https://dev.to/oeduardoal/liquidity-fragmentation-is-more-expensive-than-it-looks-42ba"&gt;Liquidity Fragmentation Is More Expensive Than It Looks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>cryptocurrency</category>
      <category>systemdesign</category>
      <category>web3</category>
    </item>
    <item>
      <title>Monorepo - Single Repository, many modules</title>
      <dc:creator>Victor Eduardo Oliveira</dc:creator>
      <pubDate>Mon, 29 Jun 2020 20:29:36 +0000</pubDate>
      <link>https://dev.to/oeduardoal/monorepo-single-repository-many-modules-3d99</link>
      <guid>https://dev.to/oeduardoal/monorepo-single-repository-many-modules-3d99</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dvmghfirfa02sbwb71c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dvmghfirfa02sbwb71c.png" alt="image" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1 - Monorepos, porquê dessa abordagem?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What and Why?
&lt;/h3&gt;

&lt;p&gt;Um monorepo é um conceito de arquitetura, que basicamente é o que diz.&lt;/p&gt;

&lt;p&gt;Em vez de gerenciar vários repositórios, você mantém todas as suas partes de código isoladas dentro de um repositório.&lt;/p&gt;

&lt;p&gt;Monorepo não tem nada em comum com monolíticos.&lt;/p&gt;

&lt;p&gt;Você pode manter Vários tipos de aplicativos lógicos em um repositório; por exemplo, um site e seu aplicativo para iOS.&lt;/p&gt;

&lt;p&gt;Este conceito é relativamente antigo e apareceu cerca de uma década atrás.&lt;/p&gt;

&lt;p&gt;Você pode perguntar, se ele existe há uma década, então por que esse assunto é tão popular agora? &lt;/p&gt;

&lt;p&gt;Principalmente, ao longo dos últimos 5-6 anos, muitas coisas sofreram mudanças. ES6, pré-processadores SCSS, gerenciadores de tarefas, npm etc.&lt;/p&gt;

&lt;p&gt;atualmente, para manter um pequeno aplicativo baseado no React, você precisa lidar com vários pacotes, suítes de testes, scripts de CI/CD, configurações do Docker sei lá o que mais.&lt;/p&gt;

&lt;p&gt;Imagine que você precise manter várias apps.&lt;/p&gt;

&lt;p&gt;Cada pacote precisará ter sua própria configuração de ambiente, significa que toda vez que você desejar criar um novo pacote, precisará tudo de novo, copiar todos os arquivos e assim por diante.&lt;/p&gt;

&lt;p&gt;Outro exemplo, é se você precisar alterar algo build da aplicação por exemplo, vai precisar olhar cada repositório, fazer a correção, abrir um PR em todos eles, aguardar build em cada projeto, ver se deu certo.. &lt;/p&gt;

&lt;p&gt;Granularidade de configuracoes&lt;/p&gt;

&lt;p&gt;Enfim, o torna muito lento. Nessas situacoes, faz sentido usar monorepo.&lt;/p&gt;

&lt;p&gt;Em vez de ter muitos repositórios com suas próprias configurações, imagine ter uma fonte de verdade - o monorepo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;suite de testes&lt;/li&gt;
&lt;li&gt;Centralizar configurações como eslint, CI/CD&lt;/li&gt;
&lt;li&gt;Arquivo de configuração Docker&lt;/li&gt;
&lt;li&gt;Configurações Webpack&lt;/li&gt;
&lt;li&gt;Pacotes semelhantes em cada aplicativo&lt;/li&gt;
&lt;li&gt;Executando comandos semelhantes&lt;/li&gt;
&lt;li&gt;bibliotecas com componentes e vários pacotes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Não é um mundo de flores.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vantagens
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Um local para armazenar todas as configurações e testes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;você pode configurar seu CI / CD e bundler uma vez. O mesmo vale para testes de unidade, e2e e integração - seu CI poderá iniciar todos os testes sem precisar lidar com configurações adicionais.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Refatorar facilmente recursos globais.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Em vez de abrir um PR em cada repositório, ver qual ordem de alteração, abre em um só lugar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Publicação de pacotes simplificada.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se você pretende criar um lib de components e publicar isso em um registry. e os comandos são os mesmo pra isso, vale a pena usar monorepo&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gerenciamento de dependência mais fácil.&lt;/strong&gt; &lt;em&gt;package.json&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Apenas um. Não é necessário reinstalar dependências em cada repositório sempre que você desejar atualizar suas dependências.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reutilize o código com pacotes compartilhados, mas ainda sim isolados.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O Monorepo permite que você reutilize seus pacotes de outros pacotes, mantendo-os isolados um do outro. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Desvantagens
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Não há como restringir o acesso apenas a algumas partes do aplicativo&lt;/li&gt;
&lt;li&gt;Baixo desempenho do Git ao trabalhar em projetos de grande escala&lt;/li&gt;
&lt;li&gt;Maior tempo de construção e configuração. Quanto mais coisas ter que configurar, build, eslint, tests...&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Exemplos (Github)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;babel - &lt;a href="https://github.com/babel/babel" rel="noopener noreferrer"&gt;https://github.com/babel/babel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Facebook CRA - &lt;a href="https://github.com/facebook/create-react-app" rel="noopener noreferrer"&gt;https://github.com/facebook/create-react-app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;jest - &lt;a href="https://github.com/facebook/jest" rel="noopener noreferrer"&gt;https://github.com/facebook/jest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gatsby - &lt;a href="https://github.com/gatsbyjs/gatsby" rel="noopener noreferrer"&gt;https://github.com/gatsbyjs/gatsby&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nextjs - &lt;a href="https://github.com/zeit/next.js/" rel="noopener noreferrer"&gt;https://github.com/zeit/next.js/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;UAI - &lt;a href="https://github.com/belezanaweb/uai" rel="noopener noreferrer"&gt;https://github.com/belezanaweb/uai&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 2 - Lerna
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;A tool for managing JavaScript projects with multiple packages.&lt;/p&gt;

&lt;p&gt;Realmente ajuda a lidar com versões, a configurar o workflow de pacotes, publicação pro npm ou github, etc. A principal ideia do Lerna é que o projeto tenha uma pasta packages, que contém todas as partes de código isoladas.&lt;/p&gt;

&lt;p&gt;Quase todos os comandos do lerna funcionam assim: O comando é repetido para todos os pacotes, como se entrasse em cada pasta. Por exemplo, bump de versão, limpar projeto, instalar dependencias, build...&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Lerna
&lt;/h3&gt;

&lt;p&gt;Installing&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g lerna // yarn global add lerna
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Init&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna init // --independent or -i 

ls -l

-rw-r--r--  1 user  staff    288B Mar 16 12:06 .git
-rw-r--r--  1 user  staff    63B Mar 16 12:04 lerna.json
-rw-r--r--  1 user  staff    91B Mar 16 12:04 package.json
drwxr-xr-x  2 user  staff    64B Mar 16 12:04 packages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;lerna.json&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create package&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna create my-package

lerna create my-module
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Bootstrap&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna bootstap // will create links :)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Exec&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna exec -- ls // --scope or --ignore --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Clean&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna clean // delete node_modules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Publish&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna publish // for ci use --yes // npm publish and git tag&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Independent versioning&lt;br&gt;
&lt;/h3&gt;
&lt;br&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{&lt;br&gt;
  "packages": [&lt;br&gt;
    "packages/*"&lt;br&gt;
  ],&lt;br&gt;
    "version": "independent",&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Show me the code&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blog.npmjs.org/post/186494959890/monorepos-and-npm" rel="noopener noreferrer"&gt;https://blog.npmjs.org/post/186494959890/monorepos-and-npm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@chris_14660/learnin-lerna-for-monorepos-in-node-js-5b577bdbf380" rel="noopener noreferrer"&gt;https://medium.com/@chris_14660/learnin-lerna-for-monorepos-in-node-js-5b577bdbf380&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
