<?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: blabla</title>
    <description>The latest articles on DEV Community by blabla (@blablaprivacy).</description>
    <link>https://dev.to/blablaprivacy</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%2F3912159%2Fefb7b042-80a3-40b8-8a02-2b231ab4c1b5.jpg</url>
      <title>DEV Community: blabla</title>
      <link>https://dev.to/blablaprivacy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/blablaprivacy"/>
    <language>en</language>
    <item>
      <title>How I built a censorship-resistant decentralized forum with IPFS, Gun.js and Ethereum</title>
      <dc:creator>blabla</dc:creator>
      <pubDate>Mon, 04 May 2026 13:31:14 +0000</pubDate>
      <link>https://dev.to/blablaprivacy/how-i-built-a-censorship-resistant-decentralized-forum-with-ipfs-gunjs-and-ethereum-17bl</link>
      <guid>https://dev.to/blablaprivacy/how-i-built-a-censorship-resistant-decentralized-forum-with-ipfs-gunjs-and-ethereum-17bl</guid>
      <description>&lt;h2&gt;
  
  
  The problem I wanted to solve
&lt;/h2&gt;

&lt;p&gt;Every forum has a kill switch. The admin can delete your posts. &lt;br&gt;
The hosting company can pull the plug. The government can send &lt;br&gt;
a takedown notice.&lt;/p&gt;

&lt;p&gt;I wanted to build something where none of that is possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Two fully decentralized forums:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;blabla.privacy&lt;/strong&gt; (English — pixel art retro UI)&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;zonefree.x&lt;/strong&gt; (French)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both accessible via Unstoppable Domains (needs Brave browser &lt;br&gt;
or an IPFS resolver).&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;React app hosted on IPFS via 4EVERLAND. No Vercel, no AWS, &lt;br&gt;
no central server. The frontend is a static hash — &lt;br&gt;
nobody can take it down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-time P2P sync
&lt;/h3&gt;

&lt;p&gt;Gun.js for real-time data sync between browsers. &lt;br&gt;
Every user connected becomes a relay node.&lt;/p&gt;

&lt;p&gt;The tricky part: Gun uses CRDT which means deleted data &lt;br&gt;
gets republished by other peers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; client-side suppression list in localStorage. &lt;br&gt;
When a salon is deleted, its ID is added to a blacklist. &lt;br&gt;
Even if Gun republishes it, the client ignores it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identity
&lt;/h3&gt;

&lt;p&gt;Your Ethereum wallet IS your identity. No email, no password, &lt;br&gt;
no account creation. Works with MetaMask, WalletConnect, &lt;br&gt;
TrustWallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  E2E Encryption
&lt;/h3&gt;

&lt;p&gt;Private messages are encrypted with NaCl box (Curve25519).&lt;/p&gt;

&lt;p&gt;Keys are derived deterministically from your wallet signature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wallet signs the message &lt;code&gt;"ZoneFree E2E Key v1"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Signature is hashed → truncated to 32 bytes&lt;/li&gt;
&lt;li&gt;Used as seed for a Curve25519 keypair&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same wallet = same keys forever. No key server needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smart contract
&lt;/h3&gt;

&lt;p&gt;Solidity on Ethereum Mainnet. Uses Chainlink ETH/USD price &lt;br&gt;
oracle for dynamic pricing. 300 free slots, then paid &lt;br&gt;
subscription forwarded to admin wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure
&lt;/h3&gt;

&lt;p&gt;Gun.js relays running on Flux decentralized cloud &lt;br&gt;
(two independent nodes). Frontend on IPFS via 4EVERLAND. &lt;br&gt;
Domains on Unstoppable Domains (on-chain, uncensorable).&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest bugs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Gun.js CRDT republishing deleted content&lt;/strong&gt;&lt;br&gt;
Solved with a localStorage suppression list. Deleted IDs &lt;br&gt;
are never re-rendered even if Gun pushes them again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. NaCl keys lost on page refresh&lt;/strong&gt;&lt;br&gt;
Solved by persisting keys in localStorage, cleared &lt;br&gt;
on wallet disconnect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. MetaMask SES conflicts&lt;/strong&gt;&lt;br&gt;
MetaMask injects a Secure EcmaScript sandbox that breaks &lt;br&gt;
many libraries. Replaced XMTP with TweetNaCl which has &lt;br&gt;
no such conflicts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Webpack scope hoisting crashes&lt;/strong&gt;&lt;br&gt;
Arrow functions assigned to &lt;code&gt;var&lt;/code&gt; caused TDZ crashes &lt;br&gt;
in production. Fix: convert all helpers to &lt;br&gt;
&lt;code&gt;function declarations&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code rules (non-negotiable)
&lt;/h2&gt;

&lt;p&gt;To avoid all webpack/SES conflicts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always &lt;code&gt;var&lt;/code&gt; — never &lt;code&gt;const&lt;/code&gt;/&lt;code&gt;let&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Always &lt;code&gt;function()&lt;/code&gt; — never arrow functions &lt;code&gt;=&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Never spread operators — use &lt;code&gt;Object.assign&lt;/code&gt; and &lt;code&gt;.concat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CI=true npm run build&lt;/code&gt; must pass before every commit&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;🌐 blabla.privacy (Brave browser needed)&lt;/li&gt;
&lt;li&gt;🌐 zonefree.x (French version)&lt;/li&gt;
&lt;li&gt;⭐ github.com/AB-lab113/blabla-privacy (open source, GPL-3.0)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built solo. Zero budget. Zero VC. 100% decentralized. 🦈&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>web3</category>
      <category>opensource</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
