diff --git a/.env.example b/.env.example index d14ca41e..834e6bc1 100644 --- a/.env.example +++ b/.env.example @@ -23,13 +23,27 @@ CANTON_BACKEND_TOKEN= RPC_URL=http://localhost:3010/rpc +# The token registry's own options, which dev-stack.sh passes through to it when set here. +# CORS_ORIGINS defaults to exactly http://localhost:3012, so the dApp opened on any other +# spelling of that address (127.0.0.1, a published port, a LAN name) needs it listed here. +# Quoted, and no space after the comma: dev-stack.sh sources this file as a shell script, +# where an unquoted space starts a second word and aborts `up` before anything starts. +#CORS_ORIGINS='http://localhost:3012,http://127.0.0.1:3012' + ############################################################################### # Frontend ############################################################################### VITE_EXPLORER_URL=http://scan.localhost:4000 -VITE_WALLET_RPC_URL=http://localhost:3010/rpc +VITE_REGISTRY_URL=http://localhost:3013 ############################################################################### # Vercel ############################################################################### -#WALLET_SERVICE_RPC_URL=http://localhost:3010/rpc +# Both are set on the deployed project rather than in this file, and neither can be localhost +# there: the bundle asks its own origin, and only the function names the registry. +#VITE_REGISTRY_URL=/api/registry +#REGISTRY_URL=https://registry.example +# Set by the build command in dapp/frontend/vercel.json, and by any other host that serves this +# bundle: it is what makes the two VITE_ keys above required instead of falling back to localhost, +# which an https page blocks as mixed content. Never set it for a local or CI build. +#DEPLOYED_BUILD=1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..93e81659 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.dar binary diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c9209c5b..7683afe9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -42,7 +42,7 @@ jobs: cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm typecheck - - run: pnpm -r --filter '!./dapp/daml' run --if-present build + - run: pnpm build - run: pnpm knip - run: pnpm docs:check - run: pnpm run check:anatomy diff --git a/CLAUDE.md b/CLAUDE.md index 344ca9f6..cabbd1cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,6 @@ Each subproject can layer its own `CLAUDE.md` for stack-specific deltas: - [`canton-dappbooster/CLAUDE.md`](canton-dappbooster/CLAUDE.md) — L2 component authoring and file layout - [`canton-theme/CLAUDE.md`](canton-theme/CLAUDE.md) — L3 `--cnc-*` token naming convention - [`dapp/frontend/CLAUDE.md`](dapp/frontend/CLAUDE.md) — app layout and naming deltas; its seams are in [`dapp/frontend/architecture.md`](dapp/frontend/architecture.md) -- `dapp/daml/` — see its `README.md` The dApp connects through any CIP-0103 browser wallet; no wallet lives in this monorepo. This stack was developed against Carpincho, which has its own repository at @@ -39,8 +38,7 @@ Current distribution: |-------|--------|--------|--------|--------------|----------| | root | yes | shim | yes | yes | Canonical repo rules and cross-component seams. | | `canton-connect/` | yes | shim | yes | yes, plus `architecture/` | Public hook API, the machine-owned lifecycle, the picker/adapter seams; chapters for the connection machine and the popup close guard. | -| `dapp/frontend/` | yes | shim | yes | yes | Canton Coin vesting dApp; `CLAUDE.md` carries the page-owns-its-components layout and the naming rules an agent would otherwise get wrong, architecture.md its internal seams. Carries a `PROVENANCE.md` recording the vendored source. | -| `dapp/daml/` | yes | no | no | no | Single DAML package (`amulet-vesting`), vendored source, built here. Carries a `PROVENANCE.md` recording the source commit and the two integration deltas. | +| `dapp/frontend/` | yes | shim | yes | yes | DBT vesting dApp; `CLAUDE.md` carries the page-owns-its-components layout and the naming rules an agent would otherwise get wrong, architecture.md its internal seams. Carries a `PROVENANCE.md` recording the vendored source. | | `canton-dappbooster/` | yes | shim | yes | yes | L2 headless components; `CLAUDE.md` carries the folder-per-component layout an agent would otherwise get wrong, architecture.md the authoring seam (anatomy contract, L2/L3 split, Zag boundary). | | `canton-theme/` | yes | shim | yes | no | Plain-CSS theme (L3); README covers the two CSS exports, `CLAUDE.md` the `--cnc-*` naming convention an agent adding a token would otherwise invent. | @@ -61,17 +59,18 @@ A README may state that a contract exists and link to it. It may not restate it. | Category | Technology | Notes | |----------|-----------|-------| -| Languages | TypeScript, DAML, Bash | TypeScript across the JS subprojects; DAML in `dapp/daml/`; Bash and Node for the root `scripts/` and `kit/` | +| Languages | TypeScript, Bash | TypeScript across the JS subprojects; Bash and Node for the root `scripts/` and `kit/` | | Package manager | pnpm workspaces | Single root `pnpm-lock.yaml`; one root `pnpm install` links every workspace. Workspace layout, `linkWorkspacePackages` and `allowBuilds` live in `pnpm-workspace.yaml`. Root `package.json` orchestrates scripts via `pnpm -C ` | | Node | 24 | Exact version pinned via root `.nvmrc`; inherits to every Node subproject. Root and the four Node subprojects all declare `engines.node` at `>=24.15.0`, which is what jsdom 30 requires | | Container runtime | Docker | Required by the `@bootnodedev/canton-barebones` LocalNet; nothing in this repository builds an image | | LocalNet | @bootnodedev/canton-barebones | Pinned exact in root devDependencies and reached through `pnpm exec canton-barebones`, so the version is the one in `package.json`. Nothing about its config is committed: `scripts/localnet-config.mjs` scaffolds the gitignored `.canton-localnet/` from the tool's own template and turns on `validators.appUser.ui` and `sv.scanUI`, without which nginx serves no `/api/validator` or `/api/scan`. The Splice checkout and the runtime env land in `.canton-localnet/.generated/` | +| Token registry | @bootnodedev/canton-token-forge | Read-only CIP-56 registry service. A git dependency pinned to `v0.3.0` and reached through `pnpm exec canton-token-forge-registry`. Its `prepare` is what compiles `registry/dist`, so `pnpm-workspace.yaml`'s `allowBuilds` must carry the resolved git specifier or the bin resolves to nothing. `scripts/dev-stack.sh` runs it on 3013, configured entirely from the block `scripts/bootstrap-vesting.mjs` prints | | Commit linting | commitlint + husky | Enforced via root `.husky/commit-msg` | | Lint / format | Biome | One root `biome.json` and a single root `@biomejs/biome`; per-project specifics live in `overrides`. No per-subproject Biome install or config. `pnpm lint` = `biome check --error-on-warnings` (warnings fail); standalone SVG assets are excluded | | Pre-commit | lint-staged | Two passes from `.husky/pre-commit`, because only the first writes: `.lintstagedrc.format.mjs` runs root Biome (`biome check --write`) across `canton-connect/`, `canton-dappbooster/`, `canton-theme/`, `dapp/frontend/`, `kit/` and `scripts/`, then `.lintstagedrc.mjs` runs the read-only gates — the tests, the doc check and the anatomy check — concurrently. One pass would let a reformat land mid-parse | | Pre-push | tsc | Root `.husky/pre-push` runs `pnpm typecheck` (`pnpm -r run --if-present typecheck`, i.e. `tsc` in each Node subproject that defines it) | | Secret scanning | gitleaks | Shared `.husky/gitleaks.sh` runs gitleaks in the pre-commit (staged diff) and pre-push (outgoing range) hooks; the pinned version (`.gitleaks-version`) is installed by `scripts/install-gitleaks.sh`, so local and CI use the same rules. Accepted non-secret findings live in `.gitleaksignore` | -| Dead code | knip | Root `knip.json` + `pnpm knip`; gates unused files/dependencies/exports. `@bootnodedev/canton-wallet-service` is in `ignoreDependencies` because nothing runs it from a `package.json` script: `scripts/dev-stack.sh` and the README call `pnpm exec canton-wallet-service`, and knip does not read shell scripts. `postcss` is there for the reason under `kit/` and the consumer scaffold | +| Dead code | knip | Root `knip.json` + `pnpm knip`; gates unused files/dependencies/exports. `@bootnodedev/canton-wallet-service` and `@bootnodedev/canton-token-forge` are in `ignoreDependencies` because nothing runs either from a `package.json` script: `scripts/dev-stack.sh` and the README call them through `pnpm exec`, and knip does not read shell scripts. `postcss` is there for the reason under `kit/` and the consumer scaffold | | Doc reference + gate | typedoc | `kit/typedoc.json` over `canton-dappbooster` and `canton-connect`, each declaring its entry points in its own `typedoc.json` and extending `kit/typedoc.shared.json` for every option that resolves per package. `pnpm docs:check` validates without emitting; `pnpm docs:build` writes the site to `typedoc/`. One config for both, strict: every validation on, `treatValidationWarningsAsErrors` and `treatWarningsAsErrors` | | Doc rules gate | `kit/docs-check.mjs` | `pnpm docs:check` runs it after typedoc. Owns what typedoc cannot see: barrel completeness, `@example` presence and naming by tier, snippet compilation, comment width, tier caps, `@category` values, the `@throws` and anatomy-`@see` requirements, the `@param`/`@returns` refusals, and description presence on exported functions (see the splits below) | | Anatomy parity gate | `kit/check-anatomy.mjs` | `pnpm check:anatomy` checks every class and `data-*` selector in `canton-theme` against the `anatomy.parts.*` / `anatomy.states.*` strings in `canton-dappbooster`, and requires each anatomy to be reached by at least one selector. Asymmetric on purpose, for the reason its header gives: an unstyled part is a legitimate consumer hook, so there is no per-part check the other way. `aria-*` states are outside it. A styling gate, not a doc one | @@ -85,13 +84,12 @@ A README may state that a contract exists and link to it. It may not restate it. | Path | Purpose | Stack | Port | |------|---------|-------|------| -| [`dapp/daml/`](dapp/daml/) | `amulet-vesting` DAML model: factory, proposal, contract, residual claim, escrowing Canton Coin as a Splice `LockedAmulet`. Vendored from [cc-vesting-contracts](https://github.com/BootNodeDev/cc-vesting-contracts), where its scenarios stay | DAML | n/a (DAR artifact) | -| [`dapp/frontend/`](dapp/frontend/) | Canton Coin vesting dApp over the local participant. Every read and write goes through the connected CIP-0103 wallet via `canton-connect`; the operator's factory, the `AmuletRules` and the open mining round all arrive by explicit disclosure. Imported from `cn-dappbooster@feat/vesting-lite` (see its `PROVENANCE.md`). | Vite + React + Ark UI + lucide-react + Tailwind v4 + zustand + react-router + Biome | 3012 | +| [`dapp/frontend/`](dapp/frontend/) | `DBT` vesting dApp over the local participant. Every read and write goes through the connected CIP-0103 wallet via `canton-connect`; the operator's factory and the instrument's `InstrumentConfig` arrive by explicit disclosure, the config from the token registry. Imported from `cn-dappbooster@feat/vesting-lite` (see its `PROVENANCE.md`). | Vite + React + Ark UI + lucide-react + Tailwind v4 + zustand + react-router + Biome | 3012 | | [`canton-connect/`](canton-connect/) | wagmi-style React hooks wrapping the `dapp-sdk` facade; the SDK owns discovery, the picker, the session and the transports | TypeScript + React 19 + xstate 5 + Biome | n/a (library) | | [`canton-dappbooster/`](canton-dappbooster/) | L2 headless UI components for Canton dApps (tsdown-built, zero styling), plus the light/dark/system theme runtime that drives `data-theme`, plus the pure utilities the components are built on, the exact-decimal amount ones included. Styling lives in `canton-theme`. `src/index.ts` is the public API; `src/connect.ts` is the `/connect` sub-path, holding the components that read the wallet session so the main barrel stays free of the Canton SDK. | TypeScript + React 19 + tsdown + vitest + Biome | n/a (library) | | [`canton-theme/`](canton-theme/) | L3 plain-CSS theme for the kit: `--cnc-*` tokens + prestyled defaults, consumed by importing its CSS. | CSS | n/a (library) | -Two things the loop needs are not subprojects but dependencies. wallet-service ships from +Three things the loop needs are not subprojects but dependencies. wallet-service ships from [BootNodeDev/canton-wallet-service](https://github.com/BootNodeDev/canton-wallet-service), installs from npm as a root devDependency, and `scripts/dev-stack.sh` runs it on port 3010 through `pnpm exec canton-wallet-service`. The LocalNet ships from @@ -99,6 +97,12 @@ port 3010 through `pnpm exec canton-wallet-service`. The LocalNet ships from devDependency whose config `scripts/dev-stack.sh` scaffolds into the gitignored `.canton-localnet/` and drives there over `pnpm exec`. +The token registry ships from +[BootNodeDev/canton-token-forge](https://github.com/BootNodeDev/canton-token-forge), arrives as a +git dependency pinned to a tag, and `scripts/dev-stack.sh` runs it on port 3013 through +`pnpm exec canton-token-forge-registry`. That repository also builds both DARs under `vendor/`; +`vendor/PROVENANCE.md` records which release each came from. + ## Code Style - All source code in English regardless of conversation language. @@ -337,7 +341,13 @@ package, because only `canton-dappbooster` splits markup from styles across a pa - Use **pnpm** only (never npm or yarn). - This is a pnpm workspaces monorepo: one `pnpm install` from the repo root installs and links every package. There is no per-package install step. -- Run a subproject script either by `cd ` or by using `pnpm -C run