enbox
A decentralised identity wallet for managing DIDs, profiles, protocols, and permissions on the Enbox network.
Live Demo • Blue Variant • Getting Started • Testing • Architecture
Warning
This software is experimental and under active development. It has not been independently audited for security. The cryptographic primitives and key management are provided by the underlying @enbox/* SDK, which is also pre-release. Do not use this wallet to manage sensitive or high-value credentials in production. APIs, storage formats, and recovery mechanisms may change without notice between versions.
- Identity Management -- Create, edit, delete, import, and export decentralised identities (DID:DHT) with auto-generated avatars, banners, and friendly names
- Profile -- Display name, tagline, bio, avatar, and hero images stored on your personal DWN
- App Connect -- Scan QR codes to connect with Enbox-compatible applications
- Permission Control -- View and revoke permission grants from connected apps
- DID Search -- Look up any DID to view its public profile, with recent search history
- Seed Phrase Recovery -- Restore wallet vault from BIP-39 recovery phrase
- Identity Export/Import -- Portable JSON backup and restore of full identity data with export timestamps
- Responsive -- Desktop sidebar + native-feeling mobile bottom tab bar with safe area support
- Themes -- Dark and light modes with build-time product theme variants
- PWA -- Installable with offline service worker support
- Security -- PIN-locked vault, configurable auto-lock (5m / 10m / 30m / 1h / never), session persistence across tab refresh
| Layer | Technology |
|---|---|
| UI | Tailwind CSS v4 + @enbox/ui design tokens |
| Framework | React 18 + TypeScript 5 |
| Effects | Effect 3 for Enbox/DWN side effects, typed failures, and service layers |
| Client State | Zustand 5 |
| Server State | TanStack Query v5 |
| Routing | React Router v7 |
| Build | Vite 6 |
| Unit Testing | Vitest + Testing Library |
| E2E Testing | Playwright + axe-core |
| CI/CD | GitHub Actions + Codecov |
| Deployment | Cloudflare Pages |
| SDK | @enbox/agent @enbox/api @enbox/auth @enbox/protocols |
# Prerequisites: Bun (https://bun.sh)
# Install dependencies
bun install
# Start dev server (http://localhost:5173)
bun run dev
# Run unit tests
bun run test
# Run unit tests with coverage
bun run test:coverage
# Run E2E tests (requires Chromium)
npx playwright install chromium
bun run test:e2e
# Lint + type check
bun run lint
npx tsc --noEmit
# Production build
bun run buildThe wallet uses a comprehensive testing pyramid:
/\
/ \ E2E — Playwright + real Chromium
/ 26 \ Real browser, real CSS, accessibility audits
/------\
/ \
/ 509 \ Unit/Integration — Vitest + Testing Library
/ \ Components, hooks, stores, user flows
/______________\
Run with Vitest + happy-dom + Testing Library:
- Pure logic: identity generators, protocol names, constants, query keys
- UI components: Button, Dialog, Tabs, PinInput, Avatar, Card, etc.
- User flows: identity list filtering, tab navigation, backup confirmation
- Stores: auth, UI, backup seed, drag-drop state transitions
- Best practices enforced via eslint-plugin-testing-library
bun run test # Run once
bun run test:watch # Watch mode
bun run test:coverage # With v8 coverage reportRun with Playwright in real Chromium at desktop and mobile viewports:
- Setup flow: PIN creation, confirmation, mismatch rejection, keyboard capture
- Accessibility: axe-core WCAG 2.0/2.1 AA audits at both viewports
- Responsive: Desktop (1280px) and mobile (375px) smoke tests
- Screenshots: Captured automatically on failure
npx playwright install chromium # First time only
bun run test:e2e # Run all
npx playwright test --ui # Interactive modeCoverage is enforced in CI via thresholds and reported as PR comments via vitest-coverage-report-action:
| Metric | Threshold | Current |
|---|---|---|
| Statements | 25% | ~26% |
| Branches | 29% | ~31% |
| Functions | 30% | ~32% |
| Lines | 25% | ~27% |
src/
app.css # Tailwind v4 + @enbox/ui design tokens (dark + light)
App.tsx # Auth gate + routing + provider hierarchy
main.tsx # Entry point + PWA service worker
routes.tsx # Lazy-loaded route definitions
test-utils.tsx # Test rendering helpers (renderWithProviders)
components/
ui/ # Shared primitives (Button, Dialog, Tabs, Card, etc.)
identity/ # Identity card, avatar, public card
layout/ # AppShell, Sidebar, BottomNav, AppBar, OfflineBanner
features/
auth/ # Unlock, setup, restore, onboarding identity step
identities/ # List, create, edit, import, details + 5 tabs
connect/ # App Connect (QR scanner), DWeb Connect (popup)
search/ # DID lookup with search history
settings/ # Security, backup, settings hub
enbox/ # SDK integration (designed for Effect helper extraction)
effect/ # Shared Effect runtime, typed errors, services, and layers
auth-effects.ts # AuthManager creation/session Effect programs
provider.tsx # EnboxAuthProvider (AuthManager lifecycle + sync)
hooks/ # useAuth, useIdentities, useProfile, usePermissions
queries/ # TanStack Query functions + key factories
mutations/ # Identity CRUD mutations
protocols.ts # Protocol installation (dependency-ordered)
registration.ts # DWN tenant registration
stores/ # Zustand (auth, UI, backup seed, drag-drop)
lib/ # Utilities, browser Effect adapters, passkeys
__mocks__/ # Test mock factories (identities, profiles, agents)
e2e/ # Playwright E2E + accessibility specs
This fork keeps React, TanStack Query, and Zustand at the UI boundary, but moves Enbox SDK and DWN side effects into Effect programs. Public async functions remain available for existing hooks, while *Effect variants provide a future extraction path for an Enbox/Effect helper package.
Current Effect boundaries:
src/enbox/effect/errors.tsdefines typed failures for SDK, storage, DWN registration, protocol installation, and identity validation.src/enbox/effect/services.tsdefinesCurrentAgentandRegistrationTokenStoreservices plus browser and memory-backed layers.src/enbox/auth-effects.tswraps AuthManager create/connect/unlock/restore/lock flows.src/enbox/registration.ts,protocols.ts,identity-sync.ts,queries/identity-queries.ts, andmutations/identity-mutations.tsexpose composable Effect programs with Promise adapters for React Query.src/features/connect/connect-kernel.ts(the@enbox/connectkernel adapter: bounded relay fetch, approval ceremony, sealed response delivery),protocol-install.ts, andsrc/features/search/public-profile.tskeep feature-level Enbox/DWN side effects behind Effect adapters.src/lib/passkeys.tswraps WebAuthn, PRF, WebCrypto, IndexedDB, and passkey credential persistence behind Effect programs while preserving the existing public async API.src/lib/browser-effects.tscentralizes browser storage, clipboard/share, endpoint health, random UUID, and service worker registration effects used by stores, settings, search history, provider session cache, and bootstrap code.
The intended pattern is:
import { Effect } from 'effect'
const program = createIdentityEffect(params)
const result = await runEnboxPromise(
program.pipe(Effect.provide(enboxLiveLayer(agent))),
)Tests can swap the browser layer for memoryRegistrationTokenStoreLayer or direct service layers, which keeps DWN registration and token-refresh behavior deterministic.
| Type | Tool | Examples |
|---|---|---|
| Client state | Zustand | Auth status, UI preferences, sidebar state |
| Server/DWN state | TanStack Query over Effect adapters | Identities, profiles, protocols, permissions |
| URL state | React Router v7 | Current page, identity DID params |
The src/enbox/ directory is designed for future extraction into an Enbox/Effect helper library. It has zero imports from UI components or feature pages. All Enbox SDK interactions go through this layer:
App.tsx
└─ EnboxAuthProvider ← manages AuthManager lifecycle
└─ useAuth() ← connect / unlock / restore / lock
└─ useIdentities() ← TanStack Query → agent.identity.list()
└─ useProfile(did) ← TanStack Query → ProfileProtocol
└─ usePermissions(did) ← TanStack Query → DwnApi.permissions
The wallet configures the SDK's live WebSocket sync and otherwise leaves sync lifecycle management to AuthManager. It does not set a custom interval: Enbox's default five-minute durable-feed settle check is a degraded-network safety net, not a polling loop. Identity registration hot-adds live subscriptions, delivery:applied events route remote changes into the affected query caches, and getReplicationLinks() drives the caught-up/repair state shown per remote. Status refreshes are event-driven with an infrequent safety refresh; the wallet does not run its own one-shot sync passes.
Build-time product themes via VITE_PRODUCT_THEME:
| Variant | Accent | Demo |
|---|---|---|
| Default | Rose / Pink | enbox-wallet.pages.dev |
| Blue | Blue | blue-enbox-wallet.pages.dev |
VITE_PRODUCT_THEME=blue bun run buildEvery push triggers:
- Lint -- ESLint + testing-library plugin
- Typecheck --
tsc --noEmit - Unit tests -- Vitest with v8 coverage (PR comments with per-file breakdown)
- E2E tests -- Playwright in real Chromium (with failure screenshots)
- Build -- Vite production build
- Deploy -- Cloudflare Pages (main branch only, both theme variants)
| Secret | Description |
|---|---|
CLOUDFLARE_API_TOKEN |
Cloudflare API token with Pages:Edit |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare account ID |
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Run tests (
bun run test && bun run test:e2e) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
All PRs must pass CI (lint + typecheck + unit tests + E2E tests + build).
See LICENSE.