Local Development
This guide covers advanced local development topics. For basic setup, see the Quick Start.
Starting the Local Bee Cluster
Section titled “Starting the Local Bee Cluster”The project uses @snaha/bee-compose to run a local Bee cluster with blockchain support.
Quick Start
Section titled “Quick Start”# Start everything: cluster and chain in background, identity UI and demo in foregroundpnpm dev:local
# The same, from a clean chain and empty node statepnpm dev:local:fresh
# Tear the containers downpnpm dev:local:stopRe-running pnpm dev:local is a no-op for whatever is already up. To drive the pieces separately,
pnpm dev:cluster:start starts just the containers and pnpm dev just the servers.
All Cluster Commands
Section titled “All Cluster Commands”| Command | Description |
|---|---|
pnpm dev:cluster:start |
Start cluster in background (queen + 3) |
pnpm dev:cluster:start --fresh |
Clean start (destroys node state) |
pnpm dev:cluster stop |
Stop the cluster |
pnpm dev:cluster status |
Show what is running |
pnpm dev:cluster:logs |
Follow queen node logs |
pnpm dev:cluster passes anything through to the bee-compose CLI, so pnpm dev:cluster --help
lists the rest.
Local Endpoints
Section titled “Local Endpoints”Once the cluster is running, these endpoints are available:
| Service | URL |
|---|---|
| Queen Bee API | http://localhost:1633 |
| Worker 1 API | http://localhost:16331 |
| Blockchain RPC | http://localhost:9545 |
Every node in the cluster allows all CORS origins, so the identity UI can talk to
http://localhost:1633 as soon as it is up. A Bee node you run yourself instead needs the identity
UI’s origin allowed explicitly — see Using Your Own Bee Node.
Developer Tools (/dev)
Section titled “Developer Tools (/dev)”The Identity UI includes a Developer Tools page at http://localhost:5500/dev with utilities for testing and debugging.
A menu in the page header switches the whole app between the local endpoints and the production ones in a click; anything else is edited through the product’s own Network settings dialog. Below it, a banner names the chain the configured RPC actually serves — proven by its genesis hash, since the local chain reports Gnosis’ chain id on purpose — so it is always visible whether the tools below would be spending real money.
Overview Tab
Section titled “Overview Tab”- Endpoint Status — Live indicators showing connectivity to local Bee endpoints
- Test Connect Flow — Quick link to test the authentication flow with the demo app
- Local Data Summary — Shows counts of accounts, identities, connected apps, and stamps
- Clear All Data — Wipes all local storage (accounts, identities, stamps)
Chain Tab
Section titled “Chain Tab”Everything that talks to the Gnosis-side chain, acting on the account selected at the top of the page. These tools refuse to run anywhere but a chain that is provably a development one.
- Buying a drive — Add drive offers two payment methods. The built-in engine — which the paid drive operations (extend, resize) also use — buys for real, against whichever chain the app is pointed at. Locally that is the baked dev chain, so the same code path as mainnet runs on a chain whose funds are worth nothing. See Paying for a Drive. The other method, fund.bzz.limo, settles on mainnet only; locally the simulated purchase below stands in for it.
- Simulated purchase — makes the fund.bzz.limo method of Add drive settle a mocked batch instead of a real cross-chain payment. That widget only settles on mainnet, so this is what makes that method reachable locally at all. The batch it leaves behind is fabricated, which is why extend and resize cannot act on it. Nothing here touches the built-in method.
- Wallet networks — adds the chains a payment can be signed on to MetaMask, so a balance shows
before you reach the payment screens. Gnosis Chain (fake) — the local chain at
http://localhost:9545— is the one to add. - Faucet — hands any address xDAI, BZZ, WXDAI or USDC out of the float the local chain is baked with, and shows what that address and the faucet hold. The two ERC20s are the ones a drive can be paid in, so a token payment is testable without trading for the token first.
- On-chain drive tooling — buys a batch the account’s own postage signer OWNS, running the real purchase steps (swap → approve → createBatch) against the local chain’s BZZ pool. Create drive to test with goes one step further and attaches the batch as an ordinary drive; that is the button to reach for before hand-testing extend or resize. A companion makes one that is already inside the “Expires soon” window, for testing that warning.
- Import batch by ID — reads a batch’s parameters straight from the PostageStamp contract, so any batch id works even when the configured node has never seen it. The signer key is not on chain, so paste the one the batch was bought with.
Node Tab
Section titled “Node Tab”Everything that talks to the Bee node:
- Stored stamps — the batches saved in this browser, with the fields needed to re-adopt one on a fresh account.
- Retrievability self-check — writes a throwaway single-owner chunk and reads it back, to confirm the node serves back what you write. Multi-device coordination (and reliable sync) only works where it does; a companion read-by-address runs the same check across two devices, which is the one that mirrors real coordination on a load-balanced gateway.
- Manual sync testing — triggers a sync for every account, to exercise postage-stamp utilization tracking. Detailed
[StateSync]logs go to the browser console. - Partition tuning — overrides the multi-device intent-round timings for a gateway with slower propagation.
Devices Tab
Section titled “Devices Tab”The devices registered to the selected account, and which partitions each currently holds.
Getting a Postage Batch
Section titled “Getting a Postage Batch”Postage stamps are required for uploading data to Swarm. In development, you can get stamps through the Developer Tools, via API, or through the product’s own Add drive flow, paying from a wallet — see Paying for a Drive.
Using Developer Tools (Recommended)
Section titled “Using Developer Tools (Recommended)”- Navigate to
http://localhost:5500/dev - Go to the Chain tab and select an account
- Click Create drive to test with
That buys a real batch on the local chain, owned by the account’s own postage signer, and attaches it as a drive — with no signer keys to copy between fields. To adopt a batch bought elsewhere instead, use Import batch by ID on the same tab.
Using the API
Section titled “Using the API”# Buy a stamp with amount 500000000 and depth 20curl -X POST "http://localhost:1633/stamps/500000000/20"Understanding Stamp Parameters
Section titled “Understanding Stamp Parameters”| Parameter | Description |
|---|---|
| Amount | How much BZZ to allocate (affects storage duration) |
| Depth | Determines how much data can be stored (2^depth chunks) |
For local development, the defaults (amount: 500000000, depth: 20) are sufficient for testing. The amount must exceed roughly 414720000 — the local chain’s postage price (24000) multiplied by Bee’s 17280-block (~24h) minimum validity period. Smaller amounts are rejected with an HTTP 400 error.
Paying for a Drive
Section titled “Paying for a Drive”Buying, extending and resizing a drive all cost money, and the money is real even locally: the payment is a transfer from your wallet to the batch owner on Gnosis — in xDAI, WXDAI, USDC or BZZ — which is then swapped for BZZ through a real SushiSwap pool and spent against the real PostageStamp contract. Locally all of that runs against the baked hybrid chain at the production addresses, so it is the same code path as mainnet on a chain whose funds are worth nothing.
Where the chain carries the EIP-7702 delegate, as Gnosis mainnet does, the postage calls run as one atomic transaction; the baked snapshot cannot carry it, so locally they run one at a time until Create drive to test with splices it in.
That is the payment dialog’s built-in method. The other one, fund.bzz.limo — the method offered first when buying a drive — settles on Gnosis mainnet only and has nothing to settle against here, so either pick the built-in method or turn on the Simulated purchase toggles described above.
To run the flow end to end:
- Start everything with
pnpm dev:local. - Point the app at the local endpoints: the header menu on
/devswitches Bee node and RPC to Use local in one click. Anything else is edited through the Network settings dialog, also reachable from the product’s Settings menu. - On
/dev→ Chain, use Wallet networks to add Gnosis Chain (fake) (http://localhost:9545, chain id 100) to MetaMask, then use the Faucet to stock the account you will connect with — xDAI, WXDAI, USDC or BZZ, the assets a drive can be paid in. Nothing funds the payer for you: the wallet must already hold what it pays with. - Add or manage a drive in the product UI and pay in the dialog — for a new drive, switch the method to Pay with crypto (built in, experimental) first — then connect the wallet, pick a token, and sign.
Chain id alone cannot tell the local chain from the real one — it answers as 100 deliberately — so before anything is signed the app compares genesis hashes, which a chain cannot borrow, and refuses in words when your wallet is on real Gnosis while the app is pointed at the local chain (or the reverse). That is what stops a rehearsal spending real xDAI.
Reach for pnpm dev:local:fresh when the chain has drifted — every purchase trades against a real,
thin BZZ pool, and a fresh start restores the baked snapshot. It also wipes node state, so drives
created earlier will point at batches that no longer exist; clear the UI’s site data too.
Paying from a chain other than Gnosis is not supported yet. See docs/Drive-Payment-Flow.md for the full design.
Common Gotchas
Section titled “Common Gotchas”Stamp Not Usable After Purchase
Section titled “Stamp Not Usable After Purchase”After buying a postage stamp, wait ~30 seconds before using it. The stamp needs to be registered on the blockchain before the Bee node accepts it.
Safari Storage Partitioning
Section titled “Safari Storage Partitioning”Safari’s Intelligent Tracking Prevention (ITP) partitions storage for third-party iframes, so the proxy cannot read the trusted domain’s localStorage. Uploads are designed to keep working — the connect popup hands the iframe the account’s stamps and signer keys — but nothing is persisted in the partition, so every page load re-runs the handshake. See issue #277 for the background.
Note that this path is not yet confirmed on real Safari: it is verified on Chromium and Firefox with third-party storage partitioned. pnpm dev cannot reproduce it either — the identity UI and demo are served from the same site (ports do not make them cross-site), so nothing partitions locally.
Locally, pnpm dev also starts the account-bus signaling server on port 5520; without it a partitioned iframe can only reach contexts in its own partition.
Safari private browsing sessions are also ephemeral — data is lost when the window closes.
Storage Partitioning Debugging
Section titled “Storage Partitioning Debugging”To check if storage is partitioned:
- Open browser DevTools
- Go to Application → Storage
- Look for partitioned storage indicators
On Chrome/Firefox localhost, storage should work immediately. If issues occur, try clicking the iframe first to trigger Storage Access API.
Bee Requests Blocked by CORS
Section titled “Bee Requests Blocked by CORS”The cluster started by pnpm dev:cluster:start allows every origin, so this only bites a Bee node
you run by hand: it has to allow the identity UI’s origin (http://localhost:5500 in
development), because Bee requests are made by the Swarm ID iframe rather than by the app embedding
it. See Using Your Own Bee Node.
Bee Cluster Won’t Start
Section titled “Bee Cluster Won’t Start”- Ensure Docker is running
- Check if ports 1633, 16331, or 9545 are already in use
- Try
pnpm dev:cluster:start --freshfor a clean start - After a
@snaha/bee-composeupgrade, add--pullso the chain image is rebuilt rather than reused
Library Changes Not Reflected
Section titled “Library Changes Not Reflected”If you modify code in lib/, you need to rebuild:
cd lib && pnpm build# Or for continuous rebuilding:cd lib && pnpm build:watchDevelopment Tips
Section titled “Development Tips”- TypeScript Execution: Use
pnpx tsxinstead ofnpx ts-nodeto run TypeScript files - Hot Reload: Changes in
ui/src/will automatically reload in the browser - Debugging: Use browser DevTools on both the parent page and the iframe
Client-Side Stamp Signing
Section titled “Client-Side Stamp Signing”A drive created from the /dev Chain tab is owned by the account’s derived postage signer — copy its key from Stored stamps on the Node tab. A batch bought by hand against a Bee node’s own API is owned by that node instead (the queen key below). Either way, client-side signing uses the owning key:
import { Stamper } from '@ethersphere/bee-js'
const stamper = Stamper.fromBlank(signerKey, batchId, depth)const envelope = stamper.stamp(chunk)Known Dev Keys
Section titled “Known Dev Keys”These are the pre-funded keys available in the local Bee cluster:
| Node | Private Key | Address |
|---|---|---|
| Queen | 566058308ad5fa3888173c741a1fb902c9f1f19559b11fc2738dfc53637ce4e9 |
0x26234a2ad3ba8b398a762f279b792cfacd536a3f |
| Worker 1 | 195cf6324303f6941ad119d0a1d2e862d810078e1370b8d205552a543ff40aab |
- |
Testing with Real Domains (SSH Tunnel)
Section titled “Testing with Real Domains (SSH Tunnel)”To test storage partitioning behavior with real TLS certificates (as in production), you can use SSH tunnels to a VPS with nginx.
Architecture
Section titled “Architecture”Your VPS (nginx + HTTPS) Your Local Machine┌─────────────────────────┐ ┌─────────────────────┐│ demo.yourdomain.com │◄──────────│ SSH -R tunnels ││ → 127.0.0.1:18080 │ │ 18080 → demo ││ id.yourdomain.com │ │ 5500 → identity ││ → 127.0.0.1:5500 │ │ (Vite dev server) │└─────────────────────────┘ └─────────────────────┘VPS Setup (one-time)
Section titled “VPS Setup (one-time)”- Add nginx server blocks pointing to
127.0.0.1:18080(demo) and127.0.0.1:5500(identity) - Get SSL certificates with certbot
- Add DNS A records for both subdomains
Local Usage
Section titled “Local Usage”# Terminal 1: Start demo serverpnpm dev:demo
# Terminal 2: Start SvelteKit dev server with allowed hostsVITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=id.yourdomain.com pnpm dev:ui
# Terminal 3: Open SSH tunnelssh -R 18080:localhost:3500 -R 5500:localhost:5500 user@your-vpsThe VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS environment variable is required when accessing the Vite dev server through a foreign hostname. Without it, Vite will reject requests from the tunneled domain.
Access the Demo
Section titled “Access the Demo”https://demo.yourdomain.com/?idDomain=https://id.yourdomain.comThe ?idDomain= parameter tells the demo which identity service to use. This allows testing cross-origin storage partitioning with real browser security policies while still having hot reload for the identity UI.