dapp-builder
Freenet Decentralized Application Builder
Build decentralized applications on Freenet following the architecture patterns established in River (decentralized chat).
How Freenet Applications Work
Freenet is a platform for building decentralized applications that run without centralized servers. Apps rely on a global, peer-to-peer Key-Value Store where the "Keys" are cryptographic contracts.
Core Concept: The Contract is the Key
The "Key" for any piece of data is the cryptographic hash of the WebAssembly (WASM) code that controls it.
- This ties the identity of the data to its logic
- If you change the code (logic), the key changes
- This creates a "Trustless" system: You don't need to trust the node storing the data, because the data is self-verifying against the contract code
The Three Components of a Freenet App
1. The Contract (Network Side)
- Role: Acts as the "Backend" or Database
- Location: Runs on the public network (untrusted peers)
- Functionality:
- Defines what data (State) is valid
- Defines how that data can be modified
- State: Holds the actual application data (arbitrary bytes)
- Constraint: Cannot hold private keys or secrets - all data is public (unless encrypted by the client)
2. The Delegate (Local Side)
- Role: Acts as the "Middleware" or private agent
- Location: Runs locally on the user's device (within the Freenet Kernel)
- Functionality:
- Trust Zone: Safely stores secrets, private keys, and user data
- Computation: Performs signing, encryption, and complex logic before sending data to the network
- Background Tasks: Can run continuously to monitor contracts or handle notifications even when the UI is closed
3. The User Interface (Frontend)
- Role: Interaction layer for the user
- Location: Web Browser (SPA) or native app
- Functionality:
- Connects to the local Freenet Kernel via WebSocket/HTTP
- Built using standard web frameworks (Dioxus, React, Vue, etc.)
- Agnostic to underlying P2P network complexity
Data Synchronization & Consistency
Freenet solves "Eventual Consistency" using a specific mathematical requirement:
Commutative Monoid: The function that merges updates must be a commutative monoid.
- Order Independent: It shouldn't matter what order updates arrive in
- If Peer A merges Update X then Y, and Peer B merges Update Y then X, they must end up with the same result
Efficiency: Peers exchange Summaries (compact representations) and Deltas (patches/diffs) rather than re-downloading full state.
Advanced Capabilities
- Subscriptions: Clients can subscribe to contracts and get notified of changes immediately (real-time apps)
- Contract Interoperability: Contracts reading other contracts' state is planned but not yet implemented
Development Workflow
Follow these phases in order:
Phase 1: Contract Design (Shared State)
Start by defining what state needs to be shared across all users.
Key questions:
- What data must all users see consistently?
- How should conflicts be resolved when two users update simultaneously?
- What cryptographic verification is needed?
- What are the state components and their relationships?
Implementation steps:
- Define state structure using
#[composable]macro from freenet-scaffold - Implement
ComposableStatetrait for each component - Implement
ContractInterfacetrait for the contract - Ensure all state updates satisfy the commutative monoid requirement
- Every field in state must be covered by a cryptographic signature -- contracts run on untrusted peers who can modify unsigned fields. Write a test for each signed field verifying that tampering causes verification failure. See contract-patterns.md for versioned signature patterns when adding fields later.
- Plan contract upgrade from v1. Contract keys change with every WASM hash change, so include an
OptionalUpgradepointer in state, keep serialization backwards-compatible, and maintain alegacy_contracts.tomlmigration registry. See contract-patterns.md "Contract WASM Upgrade & State Migration".
Reference: references/contract-patterns.md
Phase 2: Delegate Design (Private State)
Determine what private data each user needs stored locally.
Key questions:
- What user-specific data needs persistence? (keys, preferences, cached data)
- What signing/encryption operations are needed?
- What permissions are needed for sensitive operations?
Implementation steps:
- Define request/response message types
- Implement
DelegateInterfacetrait - Handle secret storage operations (Store, Get, Delete, List)
- Implement cryptographic operations (signing, encryption)
- Include an
ExportSecretshandler from v1 -- when delegate WASM changes, the delegate key changes and all stored secrets become inaccessible. The old delegate must be able to hand over its secrets to the new version. See delegate-patterns.md for the authorized migration pattern.
Reference: references/delegate-patterns.md
Phase 3: UI Design
Build the user interface connecting to contracts and delegates.
Key questions:
- What components/views does the app need?
- How should state synchronization work?
- What's the user flow for key operations?
Implementation steps:
- Set up Dioxus project with WASM target
- Implement WebSocket connection to Freenet gateway
- Create synchronizer for contract state subscriptions
- Implement delegate communication for private storage
- Build reactive UI components
Reference: references/ui-patterns.md
Phase 4: Build, Test, and Deploy
Set up the build system, CI, and deployment pipeline.
Implementation steps:
- Set up
Makefile.tomlwith build tasks for contract, delegate, and UI - Add a
preflighttask that runs fmt, clippy, tests, and migration checks before publish - Add GitHub Actions CI workflow (runs on push and PRs)
- Back up contract state to the delegate for network resilience
Reference: references/build-system.md
Project Structure Template
my-dapp/
├── common/ # Shared types between contract/delegate/UI
│ └── src/
│ ├── lib.rs
│ └── state/ # State definitions
├── contracts/
│ └── my-contract/
│ ├── Cargo.toml
│ └── src/lib.rs # ContractInterface implementation
├── delegates/
│ └── my-delegate/
│ ├── Cargo.toml
│ └── src/lib.rs # DelegateInterface implementation
├── ui/
│ ├── Cargo.toml
│ ├── Dioxus.toml
│ └── src/
│ ├── main.rs
│ └── components/
├── Cargo.toml # Workspace root
└── Makefile.toml # cargo-make build tasks
Reference Project
River demonstrates all patterns:
- Contracts:
contracts/room-contract/ - Delegates:
delegates/chat-delegate/ - UI:
ui/ - Common types:
common/
Key Dependencies
Track the versions River (the reference dApp) uses. Mismatched versions cause deserialization failures, missing features, and "variant index out of range" errors. Check River's workspace Cargo.toml before pinning.
As of April 2026 (River main):
# Workspace-wide (Cargo.toml)
freenet-stdlib = { version = "0.6.0", features = ["contract"] }
freenet-scaffold = "0.2.2"
freenet-scaffold-macro = "0.2.2"
# UI crate (ui/Cargo.toml): enables WebApi/WebSocket helpers
freenet-stdlib = { workspace = true, features = ["net"] }
# UI framework
dioxus = { version = "0.7.3", features = ["web"] }
The contract feature is required for contract and delegate crates targeting
wasm32-unknown-unknown. The net feature pulls in WebApi for the UI.
Improving This Skill
This skill is designed to be self-improving. When encountering issues while using this skill, agents should file GitHub issues or submit PRs to improve it.
When to File an Issue
File an issue at freenet/freenet-agent-skills when:
- Instructions are unclear or ambiguous
- Information is missing for a common use case
- Code examples don't compile or are outdated
- Patterns don't match current River implementation
- A referenced API has changed
How to File an Issue
gh issue create --repo freenet/freenet-agent-skills \
--title "dapp-builder: <brief description>" \
--body "## Problem
<describe what was unclear or incorrect>
## Context
<what were you trying to accomplish>
## Suggested Improvement
<optional: how the skill could be improved>"
Submitting a PR
For concrete improvements:
# Clone and create branch
gh repo clone freenet/freenet-agent-skills
cd freenet-agent-skills
git checkout -b improve-<topic>
# Make changes to dapp-builder/SKILL.md or references/*.md
# ... edit files ...
# Submit PR
git add -A && git commit -m "dapp-builder: <description>"
gh pr create --title "dapp-builder: <description>" \
--body "## Changes
<describe improvements>
## Reason
<why this helps>"
What Makes a Good Improvement
- Fixes factual errors or outdated information
- Adds missing patterns discovered while building a dApp
- Clarifies confusing instructions based on real usage
- Adds test examples that would have helped
- Updates code to match current Freenet/River APIs
More from freenet/freenet-agent-skills
systematic-debugging
Methodology for debugging non-trivial problems systematically. This skill should be used automatically when investigating bugs, test failures, or unexpected behavior that isn't immediately obvious. Emphasizes hypothesis formation, parallel investigation with subagents, and avoiding common anti-patterns like jumping to conclusions or weakening tests.
20release
Orchestrate a new Freenet release. Determines next version, shows changelog, confirms with user, and runs the release pipeline. Use when the user says "do a release", "new release", "release", or "/release".
14pr-creation
Guidelines for creating high-quality Freenet pull requests. This skill should be used when creating PRs for freenet-core, freenet-stdlib, or related repositories. Emphasizes quality over speed, thorough testing, and proper review process.
11pr-review
Executes comprehensive PR reviews following Freenet standards. Performs four-perspective review covering code-first analysis, testing, skeptical review, and big-picture assessment.
11local-dev
Set up and manage local Freenet development environments and interact with a running node. Use when the user wants to test contract changes locally, debug UI issues, run a local node, query connections/diagnostics, inspect the dashboard, use the WebSocket API, or iterate on a Freenet application without deploying to the live network.
2linux-test
Run integration tests that require Linux (full loopback range 127.x.x.x) via Docker. Use when a test fails on macOS with "Can't assign requested address" or when the user says "/linux-test".
2