Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 16 additions & 0 deletions .changeset/create-clip-wallet-platforms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"create-clip-wallet": patch
"@clip-wallet/extension-kit": patch
---

`npx create-clip-wallet my-wallet` makes the wallet on every platform from one `clip.config.ts` at the project root:
the browser extension (`packages/extension`), the desktop app (`packages/desktop`, Electron on
`@clip-wallet/desktop-kit`) and the phone app (`packages/mobile`, Expo on `@clip-wallet/mobile-kit`), plus the
Scaffold-HBAR dapp with `--scaffold-hbar`. New options: `--platforms`, `--id`, `--logo` (every platform's icons
rendered from one PNG or SVG with Node alone: extension sizes, `.icns`, `.ico`, Linux PNGs, tray, iOS, Android adaptive
and monochrome, splash), `--languages`, `--scaffold-hbar`; a `brand` command (`pnpm wallet:brand`); per-platform next
steps; `docs/signing.md` in every project (store accounts and code-signing variables, no secrets). The identity, the
config, the logo, `MAINNET.md`, `.env` and `.keys/` now live at the project root.

`@clip-wallet/extension-kit`: `clipWallet({ configDir })` reads the icon, `MAINNET.md` and the wallet-wide `.env` next to
a shared `clip.config.ts`; `wxt zip` names the store zip after the wallet.
18 changes: 18 additions & 0 deletions .changeset/desktop-mobile-kits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@clip-wallet/desktop-kit": patch
"@clip-wallet/mobile-kit": patch
"@clip-wallet/config": patch
"@clip-wallet/i18n": patch
"@clip-wallet/ui": patch
---

New packages: `@clip-wallet/desktop-kit` (the Electron app for macOS, Windows and Linux: main process, preloads, pages,
built-in dapp browser, `clipDesktop()` for electron-vite and `electronBuilderConfig()` for electron-builder) and
`@clip-wallet/mobile-kit` (the Expo app for iOS and Android: screens, vault host, in-app browser, `expoConfig()` and
`withClipWallet()` for Metro), both driven by clip.config.ts like `@clip-wallet/extension-kit`.

`@clip-wallet/config`: `languages`, `appId`, `scheme`, `desktop` / `mobile` id overrides, and the hosted-mode `fees` /
`usage` blocks reserved for Clip Cloud (off by default, never acted on by the kit); `platformIds()`, `enabledLanguages()`;
`@clip-wallet/config/node` with `loadClipConfigSync()` and the shared build-environment helpers.
`@clip-wallet/i18n`: `negotiateLocale` / `resolveLocale` take the languages a wallet offers; `offeredLocales()`.
`@clip-wallet/ui`: Settings → Language lists only the languages clip.config offers.
13 changes: 8 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,12 @@ jobs:
cache: pnpm
- run: pnpm install --frozen-lockfile --filter "@clip-wallet/mobile..."
- run: pnpm --filter @clip-wallet/mobile typecheck
- name: Test (vitest + jest-expo)
run: pnpm --filter @clip-wallet/mobile test
- name: Test (vitest + jest-expo; the app's code is @clip-wallet/mobile-kit)
run: pnpm --filter @clip-wallet/mobile-kit test
- name: Expo config resolves (icons, splash, plugins)
working-directory: apps/mobile
env:
NODE_OPTIONS: --conditions=development
run: npx expo config --type prebuild --json > /dev/null

services:
Expand All @@ -120,14 +122,15 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile --filter "./services/**..."
# Braces: the services and their workspace dependencies (packages/link, packages/backup-client).
- run: pnpm install --frozen-lockfile --filter "{./services/**}..."
- name: Typecheck
run: pnpm -r --filter "./services/**" typecheck
- name: Test (inside workerd, local Miniflare D1/R2; nothing remote)
run: pnpm -r --filter "./services/**" test

kit:
name: kit e2e (create-clip-wallet and the Scaffold-HBAR template, from tarballs)
name: kit e2e (create-clip-wallet on every platform, and the Scaffold-HBAR template, from tarballs)
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
Expand All @@ -144,7 +147,7 @@ jobs:
run: pnpm --filter @clip-wallet/extension exec playwright install --with-deps chromium
- name: Git identity for the scaffolded projects' first commit
run: git config --global user.name "kit e2e" && git config --global user.email "kit-e2e@users.noreply.github.com"
- name: create-clip-wallet from the packed tarballs
- name: create-clip-wallet from the packed tarballs (extension, desktop, mobile)
run: pnpm kit:e2e
- name: Scaffold-HBAR template (reuses the tarballs)
run: pnpm kit:e2e:scaffold-hbar --no-pack
1 change: 0 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ jobs:
- packages
- apps/extension/src
- apps/extension/scripts
- apps/mobile/src
- services
- tools
paths-ignore:
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ jobs:
- name: Typecheck and unit tests
run: |
pnpm --filter @clip-wallet/desktop typecheck
pnpm --filter @clip-wallet/desktop test
pnpm --filter @clip-wallet/desktop-kit typecheck
pnpm --filter @clip-wallet/desktop-kit test

- name: Harness
if: runner.os == 'Linux'
Expand All @@ -77,6 +78,9 @@ jobs:

- name: End-to-end (Playwright, real app)
working-directory: apps/desktop
env:
# The e2e helpers import workspace packages from source (nothing is built to dist/ in this job).
NODE_OPTIONS: --conditions=development
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
xvfb-run --auto-servernum -- pnpm exec playwright test
Expand All @@ -97,6 +101,8 @@ jobs:
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
CSC_IDENTITY_AUTO_DISCOVERY: "false"
# electron-builder.config.cjs reads clip.config.ts through @clip-wallet/desktop-kit's TypeScript source.
NODE_OPTIONS: --conditions=development
run: pnpm exec electron-builder ${{ matrix.dist }} --config electron-builder.config.cjs --publish never

- name: Smoke-test the packaged app (starts, serves the wallet page, quits)
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/repro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 1
# Full history: the script bundles the commit for a clean clone in each container, and a bundle of a
# shallow commit can't be fetched (its parents are missing).
fetch-depth: 0
- name: repro-check
env:
REPRO_OUT: ${{ runner.temp }}
Expand Down
45 changes: 34 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
Clip Wallet is a non-custodial wallet for every CLPR network: 14 network families, 1Mask for every dapp, decoded
approvals, a security floor, features (staking, swaps, on-ramps, Secure Trade), social (contacts, Clip handles,
notifications, Discover), sandboxed Clip Plugins, and route-and-fund / settle-on-Hedera on CLPRouter. It is also a
kit: the `@clip-wallet/*` packages, `@clip-wallet/extension-kit` and `create-clip-wallet` let anyone ship their own
wallet. Pre-release: test networks only.
kit: the `@clip-wallet/*` packages, `@clip-wallet/extension-kit`, `@clip-wallet/desktop-kit`, `@clip-wallet/mobile-kit`
and `create-clip-wallet` let anyone ship their own wallet on every platform from one `clip.config.ts`. Pre-release: test
networks only.

## Rules that never break
1. Only `packages/vault` touches seed phrases or private keys. `tools/harness/check.mjs` fails otherwise.
Expand All @@ -21,20 +22,23 @@ wallet. Pre-release: test networks only.
```bash
pnpm install && pnpm typecheck && pnpm test && pnpm harness
```
Touching the extension: also `pnpm --filter @clip-wallet/extension e2e`. Touching packaging, `create-clip-wallet` or
`templates/`: also `pnpm pack-all && pnpm kit:e2e` (and `pnpm kit:e2e:scaffold-hbar` for the template).
Touching the extension: also `pnpm --filter @clip-wallet/extension e2e`. Touching the desktop app or desktop-kit: also
`pnpm --filter @clip-wallet/desktop e2e`. Touching packaging, `create-clip-wallet`, the kits or `templates/`: also
`pnpm pack-all && pnpm kit:e2e` (every platform from tarballs; and `pnpm kit:e2e:scaffold-hbar` for the template).

Release work (Docker for the last one): `pnpm --filter @clip-wallet/extension package`, `actionlint`, `scripts/repro-check.sh`.

## Layout
```
packages/core shared types (the contract); additive changes only
packages/config clip.config.ts schema (zod): identity, theme, networks, route, services, mainnet
packages/config clip.config.ts schema (zod): identity, theme, networks, languages, app ids, scheme, route, services, mainnet; /node loader
packages/vault phrase, derivation for 14 families, encryption, approval-bound signing, passkey unlock
packages/chains-* evm hedera solana bitcoin sui aptos cardano substrate starknet ton near stellar tezos algorand
packages/1mask dapp connectors for all families + WalletConnect; announces the wallet's identity
packages/engine environment-free orchestration (approvals, portfolio, catalog, wiring) shared by extension and mobile
packages/extension-kit the browser extension as a library: background, pages, WXT config (clipWallet()), security floor
packages/desktop-kit the desktop app as a library: main process, preloads, pages, clipDesktop() (electron-vite), electronBuilderConfig()
packages/mobile-kit the phone app as a library: screens, background host, in-app browser, expoConfig(), withClipWallet() (Metro)
packages/ui React screens and theme tokens
packages/i18n translation layer (English + 11 languages)
packages/route CLPRouter route-and-fund; settle-on-Hedera (Phase 3) client
Expand All @@ -48,9 +52,10 @@ packages/link linked devices: phone/desktop as signer, encrypted sync
packages/connect Clip Connect (@clip-wallet/connect): wallet-agnostic dapp SDK; public standards only, never wallet internals
packages/kit-modules modules for ecosystem pickers (NEAR Wallet Selector, Stellar Wallets Kit, use-wallet, Beacon)
packages/backup-client, packages/media-client clients for services/backup and services/media-proxy
packages/create-clip-wallet npx create-clip-wallet: the template + identity + listing drafts + mainnet check
packages/create-clip-wallet npx create-clip-wallet: the template, platforms (--platforms), identity, icons from one logo, listing drafts, mainnet check
apps/extension Clip Wallet's own extension: identity + one-line entrypoints on extension-kit (e2e lives here)
apps/mobile Expo app on engine
apps/desktop Clip Wallet's own desktop app: identity + one-line entrypoints on desktop-kit (Playwright e2e lives here)
apps/mobile Clip Wallet's own phone app: identity + index.ts / app.config.ts / metro.config.js on mobile-kit
services/backup, services/media-proxy, services/link-relay Cloudflare Workers (optional hosted services)
brand/ Clip Wallet's brand sources; tools/brand/render.mjs renders the icons
contracts/handles ClipHandles on Hedera (Foundry)
Expand Down Expand Up @@ -109,6 +114,18 @@ Each ends with `pnpm typecheck && pnpm test && pnpm harness`. Product context: `
(`src/globals.ts`); the resolved config is the `virtual:clip-wallet/config` module (`src/config.ts`).
3. Tests in `packages/extension-kit/test` (vitest aliases the virtual module to `test/clip.config.ts`); then `pnpm --filter @clip-wallet/extension e2e`.

### Change the desktop or phone app
1. The code lives in `packages/desktop-kit/src` (main process, preloads, pages) and `packages/mobile-kit/src` (screens,
background host, in-app browser), shared with every kit-built wallet; `apps/desktop` and `apps/mobile` only hold
Clip Wallet's identity, icons and one-line entrypoints.
2. The resolved config is `virtual:clip-wallet/config` (default export + `icon` data URI): `clipDesktop()` in
`packages/desktop-kit/src/electron-vite.ts` provides it to electron-vite, `withClipWallet()` in
`packages/mobile-kit/src/metro.cjs` to Metro; ids and names come from `platformIds()` (`packages/config`), never a
literal. Node-side helpers (`electron-vite.ts`, `builder.ts`, `expo.ts`) have no relative imports: build tools load
them from source.
3. Tests in each kit's `test/` (vitest and, for mobile, jest-expo; both alias the virtual module to `test/clip.config.ts`);
then `pnpm --filter @clip-wallet/desktop e2e` for the desktop app, and `pnpm kit:e2e` (both platforms, from tarballs).

### Security checks
1. A new threat source implements `ThreatIntelProvider` (`packages/security/src/threat/types.ts`); say in its comment exactly what leaves the device.
2. Anything that sends user data to a third party is off unless a key is configured, and Settings → Security says so.
Expand Down Expand Up @@ -142,8 +159,14 @@ Each ends with `pnpm typecheck && pnpm test && pnpm harness`. Product context: `

### Change the Scaffold-HBAR template or create-clip-wallet
1. The template is `templates/scaffold-hbar-clip-wallet` (laid out as create-scaffold-hbar expects: `template.json`
capabilities nextjs-app / no Solidity / pnpm, rename map, outro). create-clip-wallet bundles it at `prepack`.
capabilities nextjs-app / no Solidity / pnpm, rename map, outro). It holds every part: one `clip.config.ts` at the
root, `packages/extension`, `packages/desktop`, `packages/mobile`, `packages/nextjs`. create-clip-wallet bundles it
at `prepack`.
2. Both paths must keep producing the same project: create-clip-wallet copies the template the way create-scaffold-hbar
does (`packages/create-clip-wallet/src/template.mjs`), then runs the identity step that `pnpm wallet:identity` runs.
3. `tools/harness/check.mjs` is copied into the template: `node tools/release/sync-template.mjs` after changing it.
4. Verify: `pnpm --filter create-clip-wallet test`, `pnpm pack-all`, `pnpm kit:e2e`, `pnpm kit:e2e:scaffold-hbar`.
does (`packages/create-clip-wallet/src/template.mjs`), removes the parts that weren't chosen (`selectParts()`:
folders, root scripts, and docs blocks marked `platform:<part>` / `only:<part>`), then runs the identity step that
`pnpm wallet:identity` runs (`--scaffold-hbar` with every platform = the template unchanged).
3. Icons come from one logo through `packages/create-clip-wallet/src/icons.mjs` (Node built-ins only); the files it
writes must stay the ones `electronBuilderConfig()` and `expoConfig()` point at (`test/icons.test.ts` checks).
4. `tools/harness/check.mjs` is copied into the template: `node tools/release/sync-template.mjs` after changing it.
5. Verify: `pnpm --filter create-clip-wallet test`, `pnpm pack-all`, `pnpm kit:e2e`, `pnpm kit:e2e:scaffold-hbar`.
2 changes: 0 additions & 2 deletions apps/desktop/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@ out/
release/
test-results/
playwright-report/
# Built from @clip-wallet/1mask at build/typecheck/test time.
src/preload/inpage.generated.ts
10 changes: 8 additions & 2 deletions apps/desktop/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# Clip Wallet desktop (macOS, Windows, Linux)

> The app's code is [`@clip-wallet/desktop-kit`](../../packages/desktop-kit) (`packages/desktop-kit`), shared with every
> kit-built wallet; this folder is Clip Wallet's brand on it: `clip.config.ts`, the icons rendered from `brand/`,
> `electron.vite.config.ts` (`clipDesktop()`), `electron-builder.config.cjs` (`electronBuilderConfig()`), one-line
> entrypoints, and the Playwright e2e. Paths in the tables below (`src/…`, `scripts/build-inpage.mjs`) are in the kit;
> its unit tests run with `pnpm --filter @clip-wallet/desktop-kit test`.

Electron app with the shared wallet engine in the main process, the extension's screens (`@clip-wallet/ui`) in the
wallet and approval windows, and a built-in dapp browser where 1Mask is injected the way the extension injects it.
Test networks only (`clip.config.ts`, `mainnet: false`).

```
pnpm --filter @clip-wallet/desktop build # electron-vite → out/ (main, sandboxed preloads, renderer)
pnpm --filter @clip-wallet/desktop start # run the built app
pnpm --filter @clip-wallet/desktop test # unit tests (IPC schema, origin binding, storage, Touch ID PRF, HID relay, i18n QA)
pnpm --filter @clip-wallet/desktop-kit test # unit tests (IPC schema, origin binding, storage, Touch ID PRF, HID relay, i18n QA, builder mapper)
pnpm --filter @clip-wallet/desktop e2e # build + Playwright _electron end-to-end (real vault, testnets)
pnpm --filter @clip-wallet/desktop dist:mac # electron-builder: dmg + zip, arm64 + x64 (dist:win, dist:linux, dist:dir)
CLIP_EXTENSION_IDS=<id>[,<id>] pnpm … build # Chromium extension ids allowed to use Clip Desktop (native messaging)
Expand All @@ -17,7 +23,7 @@ CLIP_EXTENSION_IDS=<id>[,<id>] pnpm … build # Chromium extension ids allowed

| Piece | Where | Notes |
|---|---|---|
| Engine + vault | `src/main/host/wallet.ts` | `@clip-wallet/engine` like the phone (`apps/mobile/src/background/host.ts`); the only place in this app that imports `@clip-wallet/vault` (harness allowlist). Features, Ledger/Keystone, social, security attached as on mobile. |
| Engine + vault | `src/main/host/wallet.ts` | `@clip-wallet/engine` like the phone (`packages/mobile-kit/src/background/host.ts`); the only place in this app that imports `@clip-wallet/vault` (harness allowlist). Features, Ledger/Keystone, social, security attached as on mobile. |
| Storage | `src/main/storage.ts` | `vault.json`: the vault record (already Argon2id-sealed) wrapped again with Electron `safeStorage` (Keychain / DPAPI / libsecret or KWallet). `app.json`: public app data. Atomic writes. |
| IPC | `src/shared/ipc.ts`, `src/main/ipc-guard.ts`, `src/preload/wallet.ts` | One typed bridge object (`window.clipDesktop`) via `contextBridge`. Main checks zod schema, then the sender (registered window role + top frame + `clip-app://wallet` origin), then the engine's own zod schema. |
| Wallet pages | `src/renderer/{wallet,approval}` | Served from `clip-app://wallet/…` (`src/main/app-protocol.ts`) with a strict CSP; only the default session knows the scheme. |
Expand Down
Loading
Loading