Skip to content

Commit ed1b3b9

Browse files
Web3NLclaude
andcommitted
docs: rewrite README for demos, enhance CLAUDE.md with architecture patterns
README minimized to focus on the handoff problem and core value prop (fully owned on-chain dapps with just II). CLAUDE.md expanded with architectural documentation: handoff flow, dapp deploy sequence, dashboard embedding, demos access codes, and icp-cli reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c7bebcc commit ed1b3b9

2 files changed

Lines changed: 125 additions & 81 deletions

File tree

CLAUDE.md

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ packages-js/ Publishable npm packages
1717
vite-plugin-canister-dapp/ Vite plugin for dev/prod environment detection
1818
1919
canisters/ Deployable canisters
20-
icp-dapp-launcher/ Installer app — SvelteKit frontend hosted as asset canister
20+
icp-dapp-launcher/ Launcher app — SvelteKit frontend hosted as asset canister
2121
wasm-registry/ On-chain registry storing dapp wasms for installation
2222
demos/ Demo access code flow — time-limited canister access for trials
2323
@@ -42,7 +42,7 @@ Developer builds a dapp using:
4242
my-canister-dashboard (Rust) ── embeds dashboard UI + management API
4343
4444
User installs dapp via:
45-
icp-dapp-launcher (installer) ── browses wasm-registry, creates canister, installs wasm
45+
icp-dapp-launcher (launcher) ── browses wasm-registry, creates canister, installs wasm
4646
4747
Developer deploys + tests via:
4848
my-canister-dapp-cli (`dapp`) ── deploy to local/mainnet with II auth, run acceptance tests
@@ -53,6 +53,110 @@ Testing validates via:
5353
Playwright E2E ── tests full flows against local ICP network
5454
```
5555

56+
## Architecture & Key Patterns
57+
58+
### The Handoff Flow (II Principal Derivation)
59+
60+
The core challenge: Internet Identity derives a *different principal* for each domain. A user at the launcher (`mycanister.app`) gets principal A. At their canister (`<id>.icp0.io`), they get principal B. Ownership must be transferred to principal B.
61+
62+
**Solution**: Re-authenticate the user with II using the `derivationOrigin` parameter set to the new canister's domain. This forces II to derive the principal the user will have when visiting their canister directly.
63+
64+
**The flow (launcher)**:
65+
1. User creates canister via the launcher app
66+
2. Launcher adds its own origin to the canister's `/.well-known/ii-alternative-origins` (certified JSON endpoint that tells II which origins are authorized)
67+
3. Launcher calls `AuthClient.login()` with `derivationOrigin = https://<canister-id>.icp0.io`
68+
4. II derives the user's principal at the canister's domain and returns it
69+
5. That principal is set as the canister controller
70+
6. Launcher removes itself from alternative origins — the bridge is gone, user fully owns the canister
71+
72+
**Key files**:
73+
- `canisters/icp-dapp-launcher/src/lib/remoteAuthentication/` — derivationOrigin construction and remote auth client
74+
- `packages-rs/my-canister-dashboard/src/dashboard/alternative_origins.rs` — alternative origins management (add/remove origins, serve certified JSON)
75+
- `packages-rs/my-canister-dashboard/src/guards/` — guard functions (`only_canister_controllers_guard`, `only_ii_principal_guard`)
76+
77+
### CLI `dapp deploy` Flow
78+
79+
The `dapp` CLI (`my-canister-dapp-cli`) deploys dapps and transfers ownership to the developer's II principal. Each deploy creates a **fresh detached canister** (new ID every time, no upgrades).
80+
81+
**Sequence**:
82+
1. Build canister wasm (or use `--wasm` to provide one)
83+
2. Create detached canister via `icp canister create --detached`
84+
3. Install wasm into the canister
85+
4. Start an ephemeral HTTP server on a random localhost port
86+
5. Add `http://localhost:<port>` to the canister's alternative origins
87+
6. Open browser for II authentication with `derivationOrigin` = canister's domain
88+
7. Receive the derived principal via POST callback to the local server
89+
8. Set the II principal on the canister
90+
9. Remove the CLI origin from alternative origins
91+
10. Update controllers to `[canister_id, user_principal]` — deployer relinquishes control (must be last step)
92+
93+
Deployments are tracked in `.dapp/deployments.jsonl`.
94+
95+
**Key files**:
96+
- `packages-rs/my-canister-dapp-cli/src/commands/deploy.rs` — full deploy orchestration
97+
- `packages-rs/my-canister-dapp-cli/src/auth/server.rs` — ephemeral auth server
98+
- `packages-rs/my-canister-dapp-cli/src/auth/page.rs` — HTML/JS served for II authentication
99+
- `packages-rs/my-canister-dapp-cli/src/icp.rs` — icp-cli wrapper struct
100+
101+
### Dashboard Embedding
102+
103+
The dashboard is a Svelte app that gets compiled and embedded into every user canister as part of the `my-canister-dashboard` Rust crate.
104+
105+
**Build process**:
106+
1. `scripts/prebuild-mcd.sh` builds the Svelte dashboard frontend
107+
2. Compiled assets (HTML, JS, CSS) are copied to `packages-rs/my-canister-dashboard/assets/`
108+
3. Rust's `include_dir!()` macro embeds these files into the crate at compile time
109+
4. At canister init, `setup_dashboard_assets()` registers them with the certified asset router
110+
111+
Every user canister serves the dashboard at `/canister-dashboard` with full HTTP certification, security headers (CSP), and the `/.well-known/ii-alternative-origins` endpoint.
112+
113+
**Key files**:
114+
- `scripts/prebuild-mcd.sh` — builds dashboard frontend and copies to assets dir
115+
- `packages-rs/my-canister-dashboard/src/dashboard/mod.rs` — asset embedding via `include_dir!()`
116+
- `packages-rs/my-canister-dashboard/src/setup/mod.rs``setup_dashboard_assets()` function
117+
- `packages-rs/my-canister-dashboard/frontend/` — Svelte dashboard source
118+
119+
### Demos Access Code Flow
120+
121+
The `demos` canister provides time-limited trial access to dapps via access codes.
122+
123+
**Access codes**: 12-character alphanumeric strings (XXXX-XXXX-XXXX), generated from IC random bytes. Characters exclude 0/O/1/I to avoid ambiguity.
124+
125+
**Redemption flow**:
126+
1. Code is atomically claimed (prevents double-use)
127+
2. A canister is taken from a pre-created pool
128+
3. Wasm is fetched from the wasm-registry and installed
129+
4. Launcher origin is added to alternative origins
130+
5. User authenticates with II (same derivationOrigin pattern)
131+
6. II principal is set, controllers updated
132+
7. Trial timer starts — on expiry, wasm is uninstalled and canister returned to pool
133+
134+
**Key files**:
135+
- `canisters/demos/src/codes.rs` — access code generation
136+
- `canisters/demos/src/redeem.rs` — redemption and finalization logic
137+
- `docs/demos-feature.md` — feature documentation
138+
139+
## icp-cli
140+
141+
`icp-cli` (the `icp` command) is the **primary CLI tool for the Internet Computer** — it replaces dfx entirely. This project does not use dfx.
142+
143+
**Configuration**: `icp.yaml` defines canisters (with build recipes), networks, and environments.
144+
145+
**Key commands used in this project**:
146+
| Command | Purpose |
147+
|---------|---------|
148+
| `icp build <name>` | Build canister wasm from icp.yaml recipe |
149+
| `icp canister create --detached` | Create a new canister, return ID immediately |
150+
| `icp canister install <id> --wasm <path>` | Install wasm into a canister |
151+
| `icp canister call <id> <method> <args>` | Call canister method with Candid-encoded args |
152+
| `icp canister settings update <id> --set-controller <principal>` | Update canister controllers |
153+
154+
**Networks**:
155+
- `local` — managed PocketIC on port 8080 with NNS + II enabled
156+
- `ic` — mainnet
157+
158+
**Environments** (defined in `icp.yaml`): `local` and `mainnet`, each specifying which canisters to deploy.
159+
56160
## Tech Stack
57161

58162
- **Backend**: Rust, ic-cdk, ic-http-certification, ic-asset-certification
@@ -104,6 +208,7 @@ npm run deps:fix # Auto-fix dependency consistency
104208
- `--clean` — clean build artifacts first
105209
- `--skip-checks` — skip lint/format/typecheck
106210
- `--skip-bootstrap` — skip local network setup (reuse existing)
211+
- `--skip-acceptance` — skip acceptance tests
107212
- `--skip-e2e` — skip E2E tests
108213
- `--include-vite-e2e` — force Vite E2E tests (even in CI)
109214

@@ -137,6 +242,9 @@ npm run release:publish # Publish npm packages + push git tags
137242
./scripts/build-all-wasm.sh # Build, shrink, and compress all canister wasms
138243
./scripts/generate-declarations.sh # Regenerate Candid declarations (dashboard, wasm-registry, demos)
139244
./scripts/clean.sh # Remove build artifacts
245+
./scripts/prebuild-mcd.sh # Build dashboard frontend + copy assets to Rust crate
246+
./scripts/add-asset-hash-entry.sh # Add asset hash entry for certification
247+
./scripts/encode-upload-arg.mjs # Encode wasm upload arguments for registry
140248
```
141249

142250
## CI/CD

README.md

Lines changed: 15 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,39 @@
11
# My Canister Dapp
22

3-
Build dapps that anyone can install and own—no CLI, no dev tools, just a browser and Internet Identity.
3+
Fully owned on-chain dapps with just Internet Identity — no CLI, no dev tools, just a browser.
44

55
[![Build Status](https://github.com/Web3NL/my-canister-dapp/workflows/Release/badge.svg)](https://github.com/Web3NL/my-canister-dapp/actions)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
77

8-
> **Status**: This project is in active development. See the [FAQ](https://mycanister.app/faq) for more information.
8+
> **Status**: Active development. Demos will be available at [mycanister.app](https://mycanister.app).
99
10-
## The Problem
10+
## The Handoff Problem
1111

12-
On the Internet Computer, canisters are deployed using the `dfx` CLI. This works well for developers, but creates a barrier for everyone else.
12+
On the Internet Computer, a single canister can be a complete application — backend, frontend, and storage in one self-contained unit. But there's no way for a non-technical user to create and own one of these canisters from a browser.
1313

14-
Canisters have a unique capability: they can serve their own frontend—like a self-contained HTTP server. This means a single canister can be a complete application. But non-technical users have no easy way to create and control one of these canisters using just a browser and their Internet Identity.
15-
16-
There's also a subtle challenge: Internet Identity derives a *different principal* for each domain. If a user authenticates at `mycanister.app` to create a canister, they get one principal. But when they visit their new canister at `abc123.icp0.io`, II gives them a *different* principal. How do you hand off ownership?
14+
There's also a subtle identity problem: Internet Identity derives a *different principal* for each domain. A user who authenticates at a launcher app gets one identity. When they visit their new canister at its own domain, they get a *different* identity. So how do you hand off ownership?
1715

1816
## The Solution
1917

20-
This repository provides libraries for developers to create **user-owned canisters**—dapps that:
21-
22-
- Can be created from a browser using only ICP and Internet Identity
23-
- Include a built-in dashboard for management (cycles, upgrades, controllers)
24-
- Are fully controlled by the user's II principal *at their canister's domain*
25-
26-
**The key insight**: After creating a canister, we re-authenticate the user with Internet Identity using the new canister's domain as the `derivationOrigin`. This produces the principal they'll use when visiting their dapp directly—and that principal becomes the controller.
27-
28-
## How It Works
29-
30-
1. **Fund**: User sends ICP to their account at the installer app
31-
2. **Create**: ICP is sent to the Cycles Minting Canister, which creates a new canister
32-
3. **Install**: The dapp's wasm is installed into the canister
33-
4. **Transfer Control**: User re-authenticates with II using the new canister domain
34-
5. **Own**: That II principal is set as the canister controller
35-
36-
The user now fully owns their canister. They can manage it directly at `<canister-id>.icp0.io/canister-dashboard`.
37-
38-
## For Developers
39-
40-
You build the wasm that users install. Your dapp becomes a template that anyone can instantiate as their own user-owned canister.
41-
42-
### What to use
18+
This project provides the libraries and tooling to create **user-owned canisters** — dapps that anyone can install and fully own on the Internet Computer using only a browser and Internet Identity.
4319

44-
**Rust (backend)**:
45-
- `my-canister-dashboard` — Embeds the dashboard UI and management endpoints
46-
- `my-canister-frontend` — Serves certified frontend assets
20+
After creating a canister, we re-authenticate the user with Internet Identity *at the new canister's domain*. This produces the principal they'll always have when visiting their dapp — and that principal becomes the controller. The launcher steps away. The user fully owns their canister.
4721

48-
**JavaScript (frontend)**:
49-
- `@web3nl/vite-plugin-canister-dapp` — Vite plugin for dev/prod environment detection
50-
- `@web3nl/my-canister-dashboard` — Utilities for interacting with dashboard endpoints
22+
### The flow
5123

52-
### Example
24+
1. **Fund** — User sends ICP to their account at the launcher
25+
2. **Launch** — A new canister is created and the dapp is installed
26+
3. **Own** — User authenticates with II at the new canister's domain. That principal becomes the controller. The canister is theirs.
5327

54-
See [examples/my-hello-world](examples/my-hello-world/) for a complete implementation.
55-
56-
```rust
57-
use my_canister_dashboard::setup::setup_dashboard_assets;
58-
use my_canister_frontend::asset_router_configs;
59-
60-
#[init]
61-
fn init() {
62-
ASSET_ROUTER.with(|router| {
63-
let mut router = router.borrow_mut();
64-
65-
// Add dashboard with allowed origins for II authentication
66-
setup_dashboard_assets(&mut router, Some(vec![
67-
"https://mycanister.app".to_string(),
68-
]));
69-
70-
// Add your frontend assets
71-
let (assets, asset_configs) = asset_router_configs(&FRONTEND_DIR);
72-
router.certify_assets(assets, asset_configs).unwrap();
73-
74-
certified_data_set(router.root_hash());
75-
});
76-
}
77-
```
28+
The user now manages their dapp directly at `<canister-id>.icp0.io/canister-dashboard` — cycles, upgrades, controllers, all from the browser.
7829

7930
## mycanister.app
8031

81-
[mycanister.app](https://mycanister.app) is the reference installer and dapp registry:
32+
[mycanister.app](https://mycanister.app) is the reference launcher and dapp registry. Browse available dapps, launch them as your own canister, and manage them — all with Internet Identity.
8233

83-
- **Dapp Store**: Browse available dapps to install
84-
- **Installer**: Create user-owned canisters with ICP + Internet Identity
85-
- **My Dapps**: View and access your created canisters
86-
87-
Developers can submit their dapps to the registry, or users can install custom wasm files directly.
88-
89-
## Packages
90-
91-
| Package | Type | Description |
92-
|---------|------|-------------|
93-
| [my-canister-dashboard](https://crates.io/crates/my-canister-dashboard) | Rust | Dashboard assets and management endpoints |
94-
| [my-canister-frontend](https://crates.io/crates/my-canister-frontend) | Rust | Certified HTTP asset serving |
95-
| [@web3nl/my-canister-dashboard](https://www.npmjs.com/package/@web3nl/my-canister-dashboard) | npm | Dashboard interaction utilities |
96-
| [@web3nl/vite-plugin-canister-dapp](https://www.npmjs.com/package/@web3nl/vite-plugin-canister-dapp) | npm | Vite plugin for environment config |
97-
98-
## Development
34+
## For Developers
9935

100-
See [CLAUDE.md](CLAUDE.md) for the full project map, commands, and architecture details.
36+
This is a monorepo containing Rust crates, npm packages, deployable canisters, and example dapps. See [CLAUDE.md](CLAUDE.md) for the full project map, architecture, and commands. Check out [examples/my-hello-world](examples/my-hello-world/) for a minimal implementation.
10137

10238
## License
10339

0 commit comments

Comments
 (0)