There is a particular kind of satisfaction in pointing a browser at a URL and knowing that no DNS registrar, hosting provider, or cloud company can take it away from you. This is a recipe for that feeling, sized for a student budget. By the end you will have a static website addressable at yourname.bzz.link, stored on the Swarm peer-to-peer network, named through the Ethereum Name Service, and updateable only with the approval of a Safe multisig. A development machine — your laptop, a CI runner, anywhere — will be able to propose updates without ever holding funds or having unilateral authority.
You will spend roughly ten US dollars in fiat. You will sign a small number of transactions on Ethereum Mainnet and Gnosis Chain. You will not need to run servers, manage TLS certificates, or trust an intermediary with the keys to your namespace. The pieces involved have been in production for years and the cost calculations below assume the gas environment of early 2026, when Ethereum mainnet typically sits below one gwei.
The architecture
How four pieces compose into one pipeline
Four protocols snap together. Understanding why each is here makes the procedure feel less like cargo-culting and more like assembly.
Swarm is a peer-to-peer storage network. You upload a directory of files; you get back a 32-byte content reference that anyone running a Bee node — or any HTTP gateway — can resolve. Storage is paid for upfront with postage stamps, which are tiny on-chain commitments on Gnosis Chain that buy your data the right to ride along on the network for a chosen volume and duration.
ENS is the Ethereum Name Service. It maps human-readable names like tortoise.eth to machine identifiers stored onchain. The relevant record for our purposes is contenthash, defined by EIP-1577, which can hold a Swarm reference, or several other content-addressed identifiers. Setting this record is what makes your name resolve to a website.
Safe is a smart-contract wallet that requires m-of-n approvals before it can act. We will put a Safe in front of the ENS name's manager role, which means publishing a new version of the website requires a multisig approval rather than a single hot key. Even better, Safe supports a feature called Proposers (formerly known as delegates) — addresses that can prepare and submit transactions to the Safe but cannot sign them. This is the key insight that lets a development environment touch the publishing pipeline without ever holding funds or controlling the namespace.
bzz.link is a public Swarm gateway maintained by the Swarm Foundation. Give it an ENS name as a subdomain — yourname.bzz.link, dropping the .eth — and it resolves the contenthash, fetches the manifest from Swarm, and serves it over HTTPS with proper origin isolation per subdomain. Same idea as eth.limo and eth.link.
┌─────────────────┐ ┌─────────────────┐
│ your laptop │ │ signing wallet │
│ (dev key, │ │ (MetaMask on │
│ no funds) │ │ desktop) │
└────────┬────────┘ └────────┬────────┘
│ propose │ sign + execute
▼ ▼
┌───────────────────────────────────┐
│ Safe (Ethereum Mainnet) │ ◀── owns ENS name's
│ 2-of-3 or 1-of-2 multisig │ manager role
└─────────────────┬─────────────────┘
│ setContenthash(node, hash)
▼
┌─────────────────┐
│ ENS resolver │ ◀── stores yourname.eth →
│ contenthash │ bzz://<swarm-ref>
└────────┬────────┘
│ resolved by
▼
┌─────────────────┐ ┌────────────┐
│ bzz.link │ ──────▶│ Swarm │
│ HTTP gateway │ fetch │ network │
└─────────────────┘ └────────────┘
▲
│ HTTPS
┌────────┴────────┐
│ visitor │
└─────────────────┘
Cost ledger
Where the ten dollars actually goes
Two chains are involved, but you only need to buy on one. Start with ~$10 of ETH on Ethereum Mainnet — that covers the ENS name, the Safe, and the Swarm storage. Later, in Step 5, you will bridge a portion directly to your Bee node's Gnosis Chain wallet using fund.ethswarm.org.
Ethereum Mainnet
- ENS name (5+ chars, 1 yr)$5.00
- ENS commit + register gas~$0.30
- Safe deploy + first tx gas~$0.75
- Manager transfer gas~$0.05
- Subtotal~$6.10
Gnosis Chain (via bridge)
- xDAI for gas~$0.50
- xBZZ for postage stamp~$3.00
- (gas on Gnosis is ~free)—
- Subtotal~$3.50
Gas on Ethereum Mainnet has been well under 2 gwei for most of 2025 and into 2026, so the figures above should hold comfortably. That said, it can spike during periods of high demand. Before sending transactions, glance at etherscan.io/gastracker — if it is above ~10 gwei, waiting an hour or two usually brings it back down.
Step 1
/// Generate a powerless dev keyThe key your laptop will hold
Before any money touches any chain, generate a fresh keypair for your development environment. This key will live in a .env file on your laptop (or in CI secrets later). It will never own ENS names, never hold tokens, and never sign anything that moves value. Its sole authority will be the right to propose transactions to the Safe you are about to create — proposals which a human still has to review and approve from a separate device.
Use Foundry's cast if you have it, or any other tool you trust:
# with foundry installed cast wallet new # or with openssl, if you want zero dependencies openssl rand -hex 32
Save the private key to a .env file as DEV_KEY. Note the corresponding address — you will need it in Step 4. Treat it as low-stakes but not zero-stakes: anyone with this key can flood your Safe's transaction queue with proposals, which is annoying but not dangerous.
Step 2
/// Get $10 of ETHFrom bank account to wallet balance
You need a signer wallet to hold funds and sign transactions. If you do not have one, install the MetaMask browser extension, write down the seed phrase on paper, and treat it as a real key from now on.
Buy roughly $10 of ETH and withdraw it to your MetaMask address on Ethereum Mainnet. The cleanest path in Europe is Mt Pelerin (Swiss, SEPA instant, no KYC for small amounts). In the US or elsewhere, Coinbase or Kraken work fine. That is all you need to buy upfront — the Gnosis Chain portion for Swarm storage will be bridged directly to your Bee node's wallet in Step 5, once you have a node address to send it to.
ENS lives on Ethereum Mainnet because that is where the registry contracts and the canonical resolution logic exist. Swarm storage is paid in BZZ, which lives on Gnosis Chain because Gnosis has near-zero fees that make per-chunk postage stamp accounting economically sane. You only need to buy once — on Mainnet — and bridge the rest in Step 5.
Step 3
/// Create the Safe with a name attachedOne tool, one name, one multisig
You could do this the long way: register an ENS name in the ENS app, create a Safe in app.safe.global, then transfer the name's manager role to the Safe in a third transaction. Three rounds of confirming, three rounds of waiting. Or you can use a tool that does all three in one flow.
onchainstartup.bzz.link exists for exactly this case. Connect your MetaMask wallet, search for an available .eth name, and let it run. The tool deploys a 1-of-1 Safe with your connected wallet as the sole signer and atomically attaches the name to it — ENS commit transaction, sixty-second wait, ENS register transaction, Safe deployment, manager transfer, all in one flow. That is a perfectly reasonable starting point. If you want stronger guarantees, you can add co-signers later in app.safe.global under Settings — for example, adding a friend's address to make it 2-of-2, or a second device of your own for 1-of-2.
onchainstartup is one of several tools in this category, and tools in this space sometimes default to testnets during development. Before paying real money, confirm in the UI that you are on Ethereum Mainnet and not Sepolia. If onchainstartup is unavailable or only on testnet, the manual three-step path through app.ens.domains and app.safe.global achieves the same end state.
When the dust settles you should have, in your wallet's records: an ENS name yourname.eth whose manager is the Safe address, and a Safe at some 0x… address that you can open in app.safe.global. Bookmark both. Your signer wallet should still have ~€2 of ETH left over — enough for the publish transactions in Step 7.
Step 4
/// Authorize the dev key as a ProposerPowerless authority, the cypherpunk way
Open your Safe in app.safe.global, connecting with your signer wallet (not the dev key). Navigate to Settings → Proposers. Click Add proposer, paste the address of the dev key you generated in Step 1, and give it a label like laptop-dev.
This change is signed by the signer wallet but recorded off-chain in the Safe Transaction Service — there is no gas cost. From this moment forward, your laptop's dev key can submit transaction proposals to the Safe via the Safe API, and those proposals will appear in the Safe UI for you (and any co-signers) to review and approve. The dev key cannot sign, cannot execute, cannot modify settings. It can only suggest.
This is the load-bearing trick of the whole setup. A CI runner, a publishing script, a future automation — any of them can hold this key without compromising the security of the namespace, because the worst they can do is propose a transaction that you then ignore.
Step 5
/// Run a Bee node and buy a postage stampPaying for storage on Gnosis
Run Bee in light mode. Light mode connects to the Swarm network without storing chunks for others, so sync time is much shorter than a full node and resource use is low — but uploads work fine. Install Bee from the official docs and start it with the --full-node=false flag.
If you would rather skip the command line entirely, Swarm Desktop (desktop.ethswarm.org) bundles a Bee node in a GUI installer and also runs on localhost:1633 — all the same commands in Step 6 work against it. The stamp purchase and upload can be done through the interface instead.
When Bee starts for the first time it generates its own Gnosis Chain wallet and prints the address in the startup log — look for a line like "address": "0x...". In Swarm Desktop it is shown in the UI. This address is separate from your MetaMask wallet. Copy it — you need it in the next step.
Now fund that node wallet. Go to fund.ethswarm.org, connect your MetaMask wallet, and enter the node's Gnosis Chain address as the destination. Choose ETH (or any other supported token) on Ethereum Mainnet as the source, and request roughly 0.5 xDAI and ~$3 worth of xBZZ to be delivered. The tool handles the swap and bridge automatically — no DEX, no manual token contract. Once the transfer lands, your node can buy a stamp.
Purchase a postage stamp batch via the API:
curl -sX POST http://localhost:1633/stamps/200000000000/17 # { # "batchID": "8fc...8552c6b", # "txHash": "0x51c...907b675" # }
Those numbers: depth 17 gives your stamp capacity for up to ~512 MB — plenty for a static site. 200000000000 is the amount per chunk in PLUR (the smallest unit of BZZ); at current prices this buys roughly 7 months of storage for around of xBZZ. Save the batchID — you will need it in Step 6.
Step 6
/// Build and upload the websiteFrom ./dist to a Swarm reference
Build your static site however you normally would. Vite, Astro, Eleventy, hand-written HTML, anything that produces a folder with index.html at the root. The example throughout this guide assumes the build output is in ./dist.
Install swarm-cli globally and upload, pointing it at your Bee node and your stamp batch:
npm i -g @ethersphere/swarm-cli swarm-cli upload ./dist \ --stamp $BATCH_ID \ --index-document index.html \ --error-document 404.html
swarm-cli prints a Swarm reference — a 64-character hex string. That string is your website on Swarm. You can verify it right now by visiting https://<reference>.bzz.link; the gateway will resolve it and serve the manifest. Save the reference; you will hand it to the proposal script in the next step.
If you intend to update the site frequently, look up Swarm feeds in the Bee docs. A feed is a mutable pointer that updates without changing its address. Wrap your initial upload in a feed and use the feed manifest reference in Step 7 instead of the raw upload reference. Future updates then bypass Step 7 entirely — you re-upload the feed with the same identity, and the gateway picks up the new content automatically. ENS never has to change again.
Step 7
/// Propose the contenthash updateThe dev key's moment
Now the dev key earns its keep. From your laptop, run a tiny Node.js script that uses the Safe API Kit to propose a setContenthash call against the ENS Public Resolver, signed by the dev key as Proposer. The proposal lands in the Safe Transaction Service and pops up in the Safe UI on your phone.
The script needs three things: the namehash of your ENS name, the EIP-1577-encoded contenthash blob, and the resolver address. The encoding for a Swarm v1 contenthash is 0xe40101fa011b20 followed by your 64-character Swarm reference — that prefix is the multicodec header for the bzz namespace plus the sha3-256 multihash for a 32-byte digest.
// propose.js — run with: node propose.js <swarm-ref> import { namehash, encodeFunctionData } from 'viem'; import SafeApiKit from '@safe-global/api-kit'; import Safe from '@safe-global/protocol-kit'; import 'dotenv/config'; const ENS_NAME = 'yourname.eth'; const SAFE_ADDRESS = '0x...'; // from Step 3 const RESOLVER = '0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63'; // ENS Public Resolver const RPC_URL = 'https://eth.llamarpc.com'; const swarmRef = process.argv[2].replace(/^0x/, ''); const contenthash = '0xe40101fa011b20' + swarmRef; const node = namehash(ENS_NAME); const data = encodeFunctionData({ abi: [{ name: 'setContenthash', type: 'function', inputs: [{ type: 'bytes32' }, { type: 'bytes' }] }], functionName: 'setContenthash', args: [node, contenthash], }); const protocolKit = await Safe.init({ provider: RPC_URL, signer: process.env.DEV_KEY, safeAddress: SAFE_ADDRESS, }); const safeTx = await protocolKit.createTransaction({ transactions: [{ to: RESOLVER, value: '0', data }], }); const safeTxHash = await protocolKit.getTransactionHash(safeTx); const signature = await protocolKit.signHash(safeTxHash); const apiKit = new SafeApiKit({ chainId: 1n }); await apiKit.proposeTransaction({ safeAddress: SAFE_ADDRESS, safeTransactionData: safeTx.data, safeTxHash, senderAddress: signature.signer, senderSignature: signature.data, }); console.log(`✓ proposed: https://app.safe.global/transactions/queue?safe=eth:${SAFE_ADDRESS}`);
Install dependencies (npm i viem @safe-global/api-kit @safe-global/protocol-kit dotenv), make sure DEV_KEY is set in .env, and run node propose.js <your-swarm-reference>. The script computes the encoded contenthash, signs the transaction hash with the dev key, and submits it to the Safe Transaction Service as a Proposer-originated proposal. No gas is spent, no tokens move; you are essentially writing a memo to the Safe.
Step 8
/// Approve and executeThe signing wallet does its job
Open the Safe in app.safe.global, connecting with your MetaMask wallet. There should be a new transaction in the queue, decoded as a call to setContenthash on the ENS resolver, with the node and hash parameters visible. Verify them. The node should match the namehash of your ENS name; the hash should start with 0xe40101fa011b20 followed by the Swarm reference you uploaded.
If they match, approve and execute. If your Safe is 1-of-2 you can execute directly. If it is 2-of-2 you will need a second signer to also approve before execution becomes available. The transaction goes onchain, the resolver updates, and ENS now points to your website.
Step 9
/// Visit the siteWhere it all becomes real
Wait a minute or two for ENS to index. Then open your browser:
https://yourname.bzz.link
Drop the .eth — the gateway expects only the label as the subdomain. The gateway resolves your ENS contenthash, fetches the Swarm manifest, and serves your index.html over HTTPS with a clean origin. You are live.
Show it to a friend. Email yourself the link. Notice that no DNS provider, hosting company, or platform is involved in serving this URL beyond the bzz.link gateway, and that even the gateway is one of several you could choose — your site is also reachable through any other Swarm gateway, and through a locally-run Bee node directly without any gateway at all.
freedombrowser.bzz.link is a fully-fledged browser that resolves ENS names natively, so you can drop the .link entirely and visit yourname.eth directly as a URL. Share that address with friends who have it installed and they will reach your site without any gateway in the URL at all.
Updating the site
The pipeline at steady state
Every future update follows the same loop, with every step amortized by automation if you want it:
- Build the new version of the site locally.
- Run
swarm-cli upload ./distto push it to Swarm. New reference comes back. - Run
node propose.js <new-reference>from your laptop. The dev key proposes a newsetContenthashcall. - Get a notification on your phone. Open Safe, verify the new reference matches what you intended, approve, execute.
- Site is updated within a minute.
None of these steps require fresh ENS purchases or Safe redeployments. The marginal cost of each update is whatever Swarm postage you consume (often pennies, sometimes free if your existing batch has capacity) and the gas for one Safe execution on Ethereum Mainnet (currently well under a dollar). The dev key still holds nothing of value. The signer wallet remains the only thing that has to be online for an actual publish to happen.
What you have built
The pieces, in retrospect
Step back and look at the property you now have: a website addressable by a name you fully own onchain, served from a network of independent storage providers, updateable only with the cooperation of a multisig you control, and proposable from a development environment that holds nothing worth stealing. The trust assumptions are small and explicit. The bzz.link gateway is convenient but replaceable. The ENS name is yours as long as you renew it (about $5/year). The Safe contract has been audited and battle-tested for years. The Swarm storage is paid for and replicated. Your signing wallet is the one thing whose physical security still matters, and that is the smallest possible attack surface for this kind of system.
This is what cypherpunk infrastructure looks like when you actually build it instead of talking about it. Ten dollars, one afternoon, and a website that does not depend on anyone's willingness to keep it online.