DEV Community

Cover image for How I built a censorship-resistant decentralized forum with IPFS, Gun.js and Ethereum
blabla
blabla

Posted on

How I built a censorship-resistant decentralized forum with IPFS, Gun.js and Ethereum

The problem I wanted to solve

Every forum has a kill switch. The admin can delete your posts.
The hosting company can pull the plug. The government can send
a takedown notice.

I wanted to build something where none of that is possible.

The result

Two fully decentralized forums:

  • 🌐 blabla.privacy (English — pixel art retro UI)
  • 🌐 zonefree.x (French)

Both accessible via Unstoppable Domains (needs Brave browser
or an IPFS resolver).

The stack

Frontend

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

Real-time P2P sync

Gun.js for real-time data sync between browsers.
Every user connected becomes a relay node.

The tricky part: Gun uses CRDT which means deleted data
gets republished by other peers.

Fix: client-side suppression list in localStorage.
When a salon is deleted, its ID is added to a blacklist.
Even if Gun republishes it, the client ignores it.

Identity

Your Ethereum wallet IS your identity. No email, no password,
no account creation. Works with MetaMask, WalletConnect,
TrustWallet.

E2E Encryption

Private messages are encrypted with NaCl box (Curve25519).

Keys are derived deterministically from your wallet signature:

  1. Wallet signs the message "ZoneFree E2E Key v1"
  2. Signature is hashed → truncated to 32 bytes
  3. Used as seed for a Curve25519 keypair

Same wallet = same keys forever. No key server needed.

Smart contract

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

Infrastructure

Gun.js relays running on Flux decentralized cloud
(two independent nodes). Frontend on IPFS via 4EVERLAND.
Domains on Unstoppable Domains (on-chain, uncensorable).

The hardest bugs

1. Gun.js CRDT republishing deleted content
Solved with a localStorage suppression list. Deleted IDs
are never re-rendered even if Gun pushes them again.

2. NaCl keys lost on page refresh
Solved by persisting keys in localStorage, cleared
on wallet disconnect.

3. MetaMask SES conflicts
MetaMask injects a Secure EcmaScript sandbox that breaks
many libraries. Replaced XMTP with TweetNaCl which has
no such conflicts.

4. Webpack scope hoisting crashes
Arrow functions assigned to var caused TDZ crashes
in production. Fix: convert all helpers to
function declarations.

Code rules (non-negotiable)

To avoid all webpack/SES conflicts:

  • Always var — never const/let
  • Always function() — never arrow functions =>
  • Never spread operators — use Object.assign and .concat
  • CI=true npm run build must pass before every commit

Try it

  • 🌐 blabla.privacy (Brave browser needed)
  • 🌐 zonefree.x (French version)
  • ⭐ github.com/AB-lab113/blabla-privacy (open source, GPL-3.0)

Built solo. Zero budget. Zero VC. 100% decentralized. 🦈

Top comments (0)