A virtual world where every Ethereum wallet address gets its own unique house. Explore, customize, and socialize in a retro-styled pixelverse.
My Ether Space is a multiplayer 2D world built entirely with vanilla JavaScript — no frameworks, no build system. Players connect their Ethereum wallet and are dropped into BlockSpace, a shared top-down world where they can walk around, visit other players' houses, chat, draw, make music, and send encrypted postcards.
Every Ethereum address automatically gets a house. You can customize your house inside and out using the built-in level editor, and visitors can interact with your space through guest books, drawing canvases, and more.
Authentication is handled entirely through Ethereum wallet signatures — no passwords, no accounts, no email. Messages between houses are end-to-end encrypted using wallet-derived keys.
- Real-time 2D multiplayer world with animated GIF sprites
- Walk around and see other players in a shared space
- Visit any house by walking up to its door
- BlockTalk — real-time chat zone near the watercooler
- Graffeth — collaborative drawing panel for the whole server
- Live Ethereum block display and block-time daily limits
- Captain system — super user elected using Speaketh Thy Mindeth
- Every Ethereum address gets a unique house
- Full level editor for customizing exterior and interior
- Custom house sprites, wallpaper, rugs, flowers, furniture, ambient sounds
- Upload custom head sprites (115x115 GIF) and body sprites
- Point-and-click for interactive room navigation
- Occupancy tracking — see who's visiting whose house in real-time
- gEth Book — Guest book where visitors leave encrypted messages
- skEth Book — Drawing canvas for visitors to sketch in your house
- MEth Drop — Piano/composition tool to record and share music
- 8-Track Player — Play and share NSF (NES Sound Format) music files
- Postcard Mailbox — Send/receive encrypted illustrated postcards between houses
- NotEth Book — Private encrypted notebook (owner only)
- EthOS — Retro Mac OS 6-style desktop with apps (LogEth, EthMail, NotEth, MyData, Manual)
- Proof of Humanity — Community voting system to verify house ownership authenticity
- Ethereum wallet authentication via MetaMask or Rabby (no passwords)
- End-to-end encryption for messages, notes, and postcards (NaCl/TweetNaCl)
- Content Security Policy headers on all pages
- CORS restrictions on all APIs
- Blocklist system for content moderation
- Address validation and HTML escaping for XSS prevention
┌─────────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ Static Frontend │ │ WebSocket Server │ │ Cloudflare R2 │
│ (Cloudflare Pages) │────▶│ (Fly.io) │────▶│ (S3-compatible │
│ │ │ Multiplayer, storage │ │ storage) │
└─────────────────────┘ └──────────────────────┘ └──────────────────┘
│
┌─────────┴──────────┐
│ │
┌────────▼──────┐ ┌─────────▼────────┐
│ Cloudflare │ │ Image Gen API │
│ Durable │ │ (Runware or │
│ Objects │ │ ImageRouter) │
│ (occupancy) │ │ (optional) │
└───────────────┘ └──────────────────┘
- Frontend: Static HTML/CSS/JS served from Cloudflare Pages (or any static host)
- WebSocket Server: Node.js on Fly.io handles multiplayer sync, R2 storage operations, image generation
- Cloudflare R2: S3-compatible object storage for house data (
houses/{address}.json), custom assets, and uploads - Cloudflare Durable Objects: Atomic house occupancy tracking (prevents race conditions for who's visiting)
- Runware / ImageRouter: Optional AI image generation for the Graffeth collaborative drawing tool, Newsmaker, and Speaketh Thy Mindeth(first message only)
- A Cloudflare account (for R2 storage, Pages hosting, and Durable Objects)
- A Fly.io account (for the WebSocket server)
- Node.js installed locally
- Wrangler CLI (
npm install -g wrangler) - Fly CLI (
curl -L https://fly.io/install.sh | sh) - Optional: A Runware or ImageRouter API key for AI image generation
git clone https://github.com/yourusername/my-ether-space.git
cd my-ether-space
npm installR2 is the storage backend for all house data, custom assets, and uploads.
wrangler login
wrangler r2 bucket create my-ether-spaceThen create R2 API tokens from your Cloudflare dashboard:
- Go to R2 > Manage R2 API Tokens
- Create a token with Object Read & Write permissions
- Save the Access Key ID and Secret Access Key
- Note your Account ID from the Cloudflare dashboard URL
- Enable public access on your bucket and note the public URL (it will look like
https://pub-xxxxx.r2.dev)
The WebSocket server handles multiplayer, storage operations, and image generation. All API keys are stored as Fly.io secrets (never in code).
cd ws-server
npm install
# Create the Fly app
flyctl launch
# Set your R2 credentials as secrets
flyctl secrets set R2_ACCOUNT_ID=your_account_id
flyctl secrets set R2_ACCESS_KEY_ID=your_access_key_id
flyctl secrets set R2_SECRET_ACCESS_KEY=your_secret_access_key
flyctl secrets set R2_BUCKET_NAME=my-ether-space
flyctl secrets set R2_PUBLIC_URL=https://pub-your-bucket-id.r2.dev
# Optional: AI image generation
flyctl secrets set RUNWARE_API_KEY=your_runware_api_key
# OR
flyctl secrets set IMAGEROUTER_API_KEY=your_imagerouter_api_key
# Deploy
flyctl deployNote your Fly.io app URL (e.g., wss://your-app-name.fly.dev).
This handles atomic occupancy tracking — ensuring only one visitor is in a house at a time.
cd cloudflare-worker
# Edit wrangler.toml and set your app name
# Then deploy:
wrangler publishNote your worker URL (e.g., https://your-worker-name.your-subdomain.workers.dev).
Update the URLs to point to your own infrastructure:
js/config.js — Set your R2 CDN and occupancy worker URLs:
export const R2_CDN = 'https://pub-your-bucket-id.r2.dev';
export const OCCUPANCY_SERVICE_URL = 'https://your-worker.your-subdomain.workers.dev';index.html, blockspace.html, mobile.html — Update the Content Security Policy meta tag to allow your URLs:
<meta http-equiv="Content-Security-Policy" content="...
connect-src 'self' wss://your-app.fly.dev https://your-worker.workers.dev https://pub-your-bucket.r2.dev https://your-account-id.r2.cloudflarestorage.com;
img-src 'self' data: blob: https://pub-your-bucket.r2.dev;
media-src 'self' blob: data: https://pub-your-bucket.r2.dev;
...">blockspace.html and js/main.js — Update the WebSocket URL:
const WS_URL = 'wss://your-app.fly.dev';cloudflare-worker/house-occupancy.js — Update CORS origins:
const allowedOrigins = [
'https://yourdomain.com',
'https://www.yourdomain.com'
];The frontend is entirely static — no build step needed.
Cloudflare Pages:
- Push your code to GitHub
- Go to Cloudflare Pages > Create Project > Connect to Git
- Set build command to empty (no build needed)
- Set output directory to
/(root) - Deploy
Or use Netlify/Vercel — same process, just point it at the repo root with no build command.
- Add a CNAME record pointing to your Cloudflare Pages URL
- Update the CORS origins in
cloudflare-worker/house-occupancy.js - Update the CSP headers in the HTML files
All secrets are stored in Fly.io — never in the codebase:
| Variable | Where | Required | Description |
|---|---|---|---|
R2_ACCOUNT_ID |
Fly.io | Yes | Your Cloudflare account ID |
R2_ACCESS_KEY_ID |
Fly.io | Yes | R2 API token access key |
R2_SECRET_ACCESS_KEY |
Fly.io | Yes | R2 API token secret |
R2_BUCKET_NAME |
Fly.io | Yes | R2 bucket name |
R2_PUBLIC_URL |
Fly.io | Yes | R2 public CDN URL |
RUNWARE_API_KEY |
Fly.io | No | Runware API key for AI image gen |
IMAGEROUTER_API_KEY |
Fly.io | No | ImageRouter API key (alternative) |
| File | What to Change |
|---|---|
js/config.js |
R2 CDN URL, occupancy service URL |
index.html |
CSP meta tag, WebSocket URL |
blockspace.html |
CSP meta tag, WebSocket URL |
mobile.html |
CSP meta tag |
js/main.js |
WebSocket URL (search for WS_SERVER_URL) |
js/mobile-app.js |
WebSocket URL |
cloudflare-worker/house-occupancy.js |
CORS allowed origins |
ws-server/admin.txt |
Your admin Ethereum addresses |
ws-server/addressBlocklist.txt |
Blocked addresses (if any) |
myetherspace/
├── index.html # Main page (wallet connect, house visit)
├── blockspace.html # Multiplayer 2D world
├── mobile.html # Mobile-optimized version
├── mobilelanding.html # Mobile redirect landing
├── _headers # Cache config (Netlify/Cloudflare)
│
├── js/ # ES6 modules (no build system)
│ ├── main.js # Main app controller
│ ├── blockspace.js # Multiplayer world engine (13,000+ lines)
│ ├── ipfs.js # R2 storage manager
│ ├── point-and-click.js # Canvas game engine
│ ├── wallet.js # Ethereum wallet connection
│ ├── encryption.js # End-to-end encryption (NaCl)
│ ├── config.js # URL configuration
│ ├── level-editor.js # House customization
│ ├── geth-book.js # Guest book
│ ├── sketh-book.js # Sketch book
│ ├── meth-drop.js # Music composition
│ ├── eight-track-player.js # NSF music player
│ ├── noteth-book.js # Encrypted notes
│ ├── ethos.js # Retro OS desktop
│ ├── proof-of-humanity.js # Humanity verification
│ ├── occupancy.js # House occupancy tracking
│ └── ... # Additional modules
│
├── css/ # Stylesheets
├── images/ # Sprites and assets
├── playerGifs/ # Player animation GIFs
├── levelEditorAssets/ # Level editor sprites
├── Audio_Ambience/ # Ambient sounds
├── libgme/ # NES music player (WebAssembly)
│
├── ws-server/ # WebSocket server (Fly.io)
│ ├── index.js # Main server
│ ├── storage.js # R2 storage + caching layer
│ ├── image-gen.js # Runware/ImageRouter integration
│ ├── fly.toml # Fly.io deployment config
│ ├── Dockerfile # Container build
│ └── package.json # Server dependencies
│
├── cloudflare-worker/ # Durable Objects worker
│ ├── house-occupancy.js # Occupancy logic
│ └── wrangler.toml # Wrangler deployment config
│
└── docs/ # Additional documentation
Storage: House data is stored as JSON in Cloudflare R2 at houses/{address}.json. Each house has a FIFO limit of 100 items per category (messages, sketches, recordings, notes, emails, postcards). Custom assets are content-addressed and deduplicated.
Multiplayer: The Fly.io WebSocket server syncs player positions, handles house state changes, and manages sessions. Players have a daily block-time limit (111 Ethereum blocks, roughly 27 minutes) tracked per address.
Encryption: Messages and notes use NaCl box encryption (x25519-xsalsa20-poly1305). Public keys are derived from wallet encryption keys via eth_getEncryptionPublicKey. Decryption requires wallet approval via eth_decrypt.
Occupancy: Cloudflare Durable Objects provide strongly consistent, atomic occupancy claims. Only one visitor can be in a house at a time. Visits auto-expire after 5 minutes; owners never expire.
My Ether Space was originally envisioned to run on IPFS for fully decentralized storage. The name ipfs.js for the storage manager reflects this original intent. The current architecture uses Cloudflare R2 as a practical alternative, but the vision remains:
- Decentralized house data — Houses stored as IPFS CIDs instead of centralized R2
- User-controlled pinning — Built-in pinning interface so users can pin their own house data, or choose to pin all data to help keep the network alive
- Content addressing — Immutable references to assets via content hashes (the content-hash deduplication in the current codebase is a step toward this)
- No single point of failure — Anyone could run a node and keep the world alive
I'd love to see this implemented. The storage layer in ipfs.js was designed with this migration in mind.
Since the site already uses Ethereum wallets for authentication and encryption, the natural next step is on-chain functionality:
- House tipping — Send ETH directly to another house's address
- In-house games with real stakes — Use smart contracts to hold funds for house-based games
- On-chain reputation — Store Proof of Humanity scores on-chain
- NFT display — Show owned NFTs inside houses
- House economy — A contract that manages house balances, enabling in-world commerce
The wallet integration is already built — it just needs a contract to talk to.
npm start
# Opens http://localhost:8080Or just open index.html in a browser. Without a WebSocket server configured, the multiplayer features won't work, but you can still explore the single-player house experience with localStorage.
- Frontend: Vanilla JavaScript (ES6 modules), no framework, no build system
- Styling: Custom CSS with NES-style retro components, Press Start 2P pixel font
- Multiplayer: WebSocket (ws library on Node.js)
- Storage: Cloudflare R2 (S3-compatible)
- Occupancy: Cloudflare Durable Objects
- Encryption: TweetNaCl (NaCl box)
- Web3: ethers.js 5.7+
- Music: libgme (WebAssembly NES/NSF player)
- Hosting: Cloudflare Pages / Fly.io
Contributions are welcome. Some areas that could use help:
- IPFS integration (see Future Vision above)
- Smart contract development
- Mobile experience improvements
- Accessibility improvements
- New house items and sprites
- Performance optimization for large BlockSpace instances
To contribute:
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
GPLv3 — see LICENSE file for details.
If you find a security vulnerability, please open an issue or contact the maintainer directly. See SECURITY_AUDIT.md for the full security review.
Donations Eth 0x8ebA166Eb2898a7E14afc6687fB4692F8EE0adF5
Donations Btc bc1qu7dxsstve8l93xwcfpaklyhsh7gvagjplnqzkg
Donations Sol 6pLqZt93M5PCEVHpkRKoTHidLFdhMuqLwUYwnJjKFuJH
