Decentralized feedback forms on Walrus + Sui. Creator-owned. Zero backend.
WalrFeed is a feedback and form platform built on Walrus decentralized storage and the Sui blockchain. Form creators build configurable forms, respondents submit feedback, and all data lives on-chain and on Walrus — no central server, no database, no vendor lock-in.
Key ideas:
- Creator-owned data. Form schemas, submissions, and storage budgets are Sui objects owned by the creator's wallet. No platform can revoke access or censor responses.
- End-to-end encryption via Seal. Sensitive submission payloads are encrypted client-side using threshold encryption (
@mysten/seal). Only authorized parties (allowlisted addresses, or after a time-lock expiry) can decrypt. - Two-blob submission model. Each submission splits into a plaintext metadata blob (for dashboard filtering/sorting without decryption) and an encrypted payload blob (full responses, media references). Both are stored on Walrus and certified atomically via a single Sui PTB.
- Sponsored gas for respondents. Respondents don't need SUI to submit. Creators fund their form's gas budget; the sponsor service co-signs transactions on the creator's behalf.
- Static hosting on Walrus Sites. The frontend is a static SPA deployed to Walrus Sites via
site-builder. No hosting provider, no CDN — the site itself is a Walrus blob.
- Frontend — Vite 8 + React 19 + TypeScript 6 + Tailwind v4 + shadcn/ui
- State — Zustand
- Routing — React Router v7
- Wallet —
@mysten/dapp-kit-react(browser wallet, no zkLogin in MVP) - Storage —
@mysten/walrus(HTTP publisher + relay) - Encryption —
@mysten/seal(t-of-n key servers) - Contracts — Sui Move 2024 edition
- Hosting — Walrus Sites via
site-builder
No server, no database, no centralized API.
| Tool | Version | Install |
|---|---|---|
| Bun | ≥ 1.3 | curl -fsSL https://bun.sh/install | bash or mise install bun@latest |
| Sui CLI | ≥ 1.71 | https://docs.sui.io/guides/developer/getting-started/sui-install |
| Docker | any recent | for local Walrus Sites portal (optional, testnet only) |
site-builder |
mainnet-latest | see Deployment |
Bun replaces both Node and npm. Vite, TypeScript, and the Mysten SDKs all run on Bun's runtime without changes.
A funded Sui wallet on testnet (and later mainnet) is needed for Move publish and Walrus blob writes. Get testnet SUI/WAL from the Sui faucet and Walrus testnet faucet.
git clone https://github.com/widnyana/WalrFeed.git
cd WalrFeed
bun install
# copy env templates and fill in package IDs after Move publish
cp .env.example .env.testnet
cp .env.example .env.mainnet
bun run devDev server runs at http://localhost:5173.
walform/
├── src/ ← React app (Vite root)
│ ├── lib/
│ │ ├── dapp-kit.ts ← createDAppKit + Walrus client extension
│ │ ├── network-config.ts ← single source of truth for testnet/mainnet
│ │ ├── sui.ts ← re-exports of dapp-kit hooks + ConnectButton
│ │ ├── walrus.ts ← useWriteBlob() + readBlob() helpers
│ │ ├── seal.ts ← encrypt/decrypt + SessionKey helpers
│ │ └── utils.ts ← shadcn `cn()` helper
│ ├── stores/
│ │ └── form-store.ts ← Zustand: in-memory form schema during build
│ ├── pages/ ← one component per route
│ ├── routes.tsx
│ ├── App.tsx
│ └── main.tsx ← provider chain: QueryClient → DAppKit → Router
│
├── move/walrfeed/ ← Sui Move package
│ ├── Move.toml
│ ├── sources/
│ │ ├── form_registry.move
│ │ ├── seal_policy.move
│ │ └── submission_index.move
│ └── tests/
│ ├── form_registry_tests.move
│ ├── seal_policy_tests.move
│ └── submission_index_tests.move
│
├── docs/ ← reference documentation
│
├── ws-resources.json ← Walrus Sites SPA routing config
├── components.json ← shadcn/ui config
├── vite.config.ts
├── tsconfig.{json,app.json,node.json}
├── package.json
└── CLAUDE.md ← project conventions for Claude Code
Everything network-scoped lives in src/lib/network-config.ts keyed on VITE_NETWORK. To switch networks, change the env file Vite reads — never hardcode URLs in components.
.env.testnet:
VITE_NETWORK=testnet
VITE_PACKAGE_ID_TESTNET=0x... # fill after `sui client publish` on testnet.env.mainnet:
VITE_NETWORK=mainnet
VITE_PACKAGE_ID_MAINNET=0x... # fill after mainnet publishbun run dev reads .env.testnet by default. bun run build:mainnet reads .env.mainnet and produces a mainnet bundle in dist/.
| What is per-network | Defined in |
|---|---|
| Sui RPC URL | SUI_RPC |
| Walrus upload-relay URL | WALRUS_UPLOAD_RELAY |
| Move package ID | PACKAGE_ID (from env) |
| Suiscan explorer base | EXPLORER |
| Epoch length (~1d testnet, ~14d mainnet) | EPOCH_DAYS |
bun run dev # vite dev server (testnet env)
bun run typecheck # tsc -b --noEmit, both project refs
bun run build # tsc -b && vite build (testnet env by default)
bun run build:testnet # explicit testnet build
bun run build:mainnet # mainnet build for site-builder deploy
bun run preview # serve ./dist locallycd move/walrfeed
sui move build # compile, surface warnings
sui move test # unit tests (sui::test_scenario)
# publish (active env decides which network)
sui client active-env # check first
sui client switch --env testnet # or mainnet
sui client publish --gas-budget 100000000
# capture the package ID into .env.<network>sui client new-address ed25519 --alias walrfeed-dev
sui client switch --address <new-address>
# fund testnet
curl -X POST https://faucet.testnet.sui.io/v1/gas \
-H 'Content-Type: application/json' \
-d "{\"FixedAmountRequest\":{\"recipient\":\"$(sui client active-address)\"}}"For mainnet, fund externally.
Walrus Sites is the deployment target. The same static SPA is built per-network and deployed via site-builder.
SYSTEM=ubuntu-x86_64-generic # or macos-arm64-generic
curl -L https://storage.googleapis.com/mysten-walrus-binaries/site-builder-mainnet-latest-$SYSTEM \
-o ~/.local/bin/site-builder && chmod +x ~/.local/bin/site-builderbun run build:testnet
site-builder --context=testnet deploy --epochs 90 ./dist
# preview on local portal
docker run --rm -p 3000:3000 ghcr.io/mystenlabs/walrus-sites-portal:testnet
# open http://<base36-id>.localhost:3000/sui client switch --env mainnet
bun run build:mainnet
site-builder --context=mainnet deploy --epochs 52 ./dist
# site object ID + base36 printed at the end → resolves at https://<base36>.wal.appSubsequent updates use the same command — site-builder reads the existing object_id from ws-resources.json and patches in place.
The architecture spec lives in docs/raw-product-design-0.5.0.md — Move structs, blob schemas, data flows, Seal policy contract. This is the authoritative reference for all contract and data-layer decisions.
/ → app landing (public)
/dashboard → forms list (authenticated, wallet required)
/dashboard/:form_id/create → form builder (authenticated)
/responses → my submitted feedback (authenticated)
/f/:form_id → public responder view + trust preview
/f/:form_id/form → fill out form
/f/:form_id/success → post-submit confirmation receipt
/dashboard/:form_id → admin dashboard (metadata mode, no decrypt)
/dashboard/:form_id/:sub_id → single submission detail (decrypt on demand)
/dashboard/:form_id/settings → form settings (epochs, budget, allowlist)
Walrus Sites routes /f/* → /f/__/index.html and /dashboard/* → /dashboard/__/index.html (placeholder shells). useParams() reads the real URL client-side.
Apache-2.0