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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ 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
- [`canton-barebones/wallet-service/CLAUDE.md`](canton-barebones/wallet-service/CLAUDE.md) — wallet-service bridge rules
- `canton-barebones/`, `dapp/daml/vesting-lite/`, `dapp/frontend/` — see each subproject's `README.md`
- [`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)
- `canton-barebones/`, `dapp/daml/vesting-lite/` — see each subproject's `README.md`

`dapp/frontend/` has no `CLAUDE.md`; its seams are in
[`dapp/frontend/architecture.md`](dapp/frontend/architecture.md).

The Carpincho wallet (CIP-0103 browser wallet) lives in its own repository at
[github.com/BootNodeDev/carpincho-wallet](https://github.com/BootNodeDev/carpincho-wallet); it is no longer part of this monorepo.
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
[github.com/BootNodeDev/carpincho-wallet](https://github.com/BootNodeDev/carpincho-wallet).

For the system shape (data flow, components, ports), see [`architecture.md`](architecture.md).

Expand All @@ -41,7 +40,7 @@ Current distribution:
| root | yes | shim | yes | yes | Canonical repo rules and cross-component seams. |
| `canton-connect/` | yes | shim | yes | yes | Public hook API, the facade's adapter/picker seams, provider event wiring. |
| `canton-barebones/wallet-service/` | yes | shim | yes | no | Local bridge rules are useful; README API boundary is enough architecture for now. |
| `dapp/frontend/` | yes | no | no | yes | Canton Coin vesting dApp; root rules suffice for authoring, but its internal seams outgrew the README. Carries a `PROVENANCE.md` recording the vendored source. |
| `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 (`vesting-lite`) plus its `daml-test` scenarios. Carries a `PROVENANCE.md` recording the vendored source. |
| `canton-barebones/` | yes | no | no | no | Docker/Bash local participant wrapper. |
| `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). |
Expand Down Expand Up @@ -77,7 +76,8 @@ A README may state that a contract exists and link to it. It may not restate it.
| Doc reference + gate | typedoc | Root `typedoc.json` over `canton-dappbooster` and `canton-connect`, each declaring its entry points in its own `typedoc.json` and extending `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 | `scripts/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 | `scripts/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 |
| Reference site | Vercel | Project `docs-canton-dappbooster` under the BootNode team, production branch `main`, built by the git integration from `pnpm docs:build`. Build settings live in `vercel.json` |
| Reference site | Vercel | Project `docs.canton-dappbooster` under the BootNode team, production branch `main`, built by the git integration from `pnpm docs:build`. Its root directory is the repo root, so the root `vercel.json` is its build settings and nobody else's |
| Demo deployment | Vercel | Project `demo.canton-dappbooster` under the same team, root directory `dapp/frontend`, so it reads `dapp/frontend/vercel.json`. A project resolves `vercel.json` relative to its own root directory, which is what keeps the two from colliding. `sourceFilesOutsideRootDirectory` is on and the build command runs from the workspace root, because a production build resolves both libraries to their `dist` rather than their source. No git integration yet, so nothing deploys on push |
| CI | GitHub Actions | `.github/workflows/pr.yml` gate on every PR (biome, typecheck+build+knip+docs, test, commitlint, gitleaks). `main` is protected: 1 approval + all checks green. `add-to-project` and `pr-assign` automate the board and PR assignee |
| Dependency updates | Renovate | `renovate.json`: non-major updates batched weekly, no auto-merge; the `@canton-network/*` SDK graph is held for manual approval on the Dependency Dashboard |

Expand Down Expand Up @@ -149,8 +149,9 @@ Placement:
never rejects a file, so each of its modules is named for what it holds (`partyId.ts`, `cx.ts`),
never `helpers.ts` or an `index.ts` barrel.
- Components live in `components/`, which is a kind folder like the rest and gets no special case.
Routed pages are the one thing kept apart, in `features/`, because the router enters them rather
than a parent composing them.
Routed pages are the one thing kept apart, in `pages/`, because the router enters them rather
than a parent composing them. A page is a consumer like any other, so what only one page renders
lives beside it.
- A component whose job is to supply context rather than render markup lives in `providers/`, named
`<Thing>Provider`, so what wraps the tree is one place to look instead of a hunt through feature
folders.
Expand All @@ -165,8 +166,8 @@ Placement:
where a caller could reasonably pick a different export, when to reach for it. Do not restate the
type. How much prose, and whether an `@example` is required at all, follows the tier table under
Doc blocks below.
- **A module has one legal spelling, and it is never relative.** `./components/toast` and
`@/components/toast` both resolved, so which one landed was down to who or what wrote the file.
- **A module has one legal spelling, and it is never relative.** `./utils/toast` and
`@/utils/toast` both resolved, so which one landed was down to who or what wrote the file.
Relative specifiers (`.`, `..`, `./*`, `../*`) are now a Biome error in `dapp/frontend`,
`canton-dappbooster`, and `canton-connect`, in all four positions: `import … from`,
`export … from`, `export *`, and dynamic `import()`.
Expand Down Expand Up @@ -334,10 +335,10 @@ package, because only `canton-dappbooster` splits markup from styles across a pa
- `pnpm run app:dev`
- `node scripts/add-component.mjs <PascalCaseName>` scaffolds a `canton-dappbooster` component
folder. Not wired into `package.json`: it is an authoring convenience, not part of the loop above.
- `node scripts/bootstrap-vesting-lite.mjs` creates the vesting operator and its factory and writes
`dapp/frontend/public/vesting-lite-parties.json`, which the dApp cannot start without. Run it after
the DAR is deployed. It takes the package id from the participant, never a default, because a stale
one shows as an empty dashboard with no error.
- `node scripts/bootstrap-vesting-lite.mjs` creates the vesting operator and its factory, which the
dApp cannot start without. Run it after the DAR is deployed. It writes no file: the dApp reads
both back off the ledger once a wallet connects, so nothing can go stale between the two, and
pointing the wallet at another participant is the whole of switching networks.
- Local ports are intentionally assigned in the `3010+` range (see table above). Do not change them without updating every subproject's defaults.
- Treat the single root `pnpm-lock.yaml` as authoritative. Do not regenerate it as part of unrelated changes, and do not reintroduce per-package lockfiles.
- `pnpm-workspace.yaml` pins `@canton-network/wallet-sdk` and `core-acs-reader` via `overrides`, at the versions wallet-service was verified against. `canton-connect`'s `@canton-network/*` deps (`dapp-sdk`, `core-types`) are not part of these overrides — they live on the ranges in its own `package.json`; bump those directly and test the connect flow, not `pnpm-workspace.yaml`. Both its `core-types` and its `dapp-sdk` devDependencies are pinned exact, not caret: Renovate's `@canton-network/**` hold only blocks version PRs, so a caret let lock file maintenance re-resolve the SDK past the hold (PR #79). The peer ranges stay caret so consumers keep a range, which is why the peer says `^1.4.0` while the pinned dev dependency is `1.5.1`.
Expand Down
20 changes: 10 additions & 10 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
| Subproject | Stack | Purpose |
| --- | --- | --- |
| `canton-barebones/` | Bash + Docker Compose + official Splice LocalNet bundle | Starts `sv + app-user`, health checks, token helper, DAR upload |
| `canton-barebones/wallet-service/` | Node 24 + Express 5 + TypeScript + `@canton-network/wallet-sdk` | Bridge Carpincho uses for external-party onboarding and participant JSON API calls |
| `canton-barebones/wallet-service/` | Node 24 + Express 5 + TypeScript + `@canton-network/wallet-sdk` | Bridge the wallet uses for external-party onboarding and participant JSON API calls |
| `dapp/frontend/` | Vite + React + Tailwind v4 + zustand + react-router | Canton Coin **vesting** dApp; every read and write goes through the connected CIP-0103 wallet via `canton-connect` |
| `dapp/daml/vesting-lite/` | DAML | `vesting-lite` DAR: the vesting factory, proposal, contract and residual-claim templates |
| `canton-connect/` | TypeScript + React 19 | wagmi-style hooks wrapping the dapp-sdk facade |
Expand All @@ -17,7 +17,7 @@
```mermaid
flowchart TD
fe["dapp/frontend<br/>http://localhost:3012"]
wallet["carpincho-wallet (separate repo)<br/>http://localhost:3011"]
wallet["CIP-0103 browser wallet (separate repo)<br/>http://localhost:3011"]
ws["wallet-service<br/>http://localhost:3010"]
au["Splice app-user<br/>JSON API http://localhost:2975"]
sv["Splice sv<br/>DSO / synchronizer side"]
Expand All @@ -43,30 +43,30 @@ containers still expose app-provider backend ports.

State boundaries:

- The CIP-0103 path: a dApp talks to Carpincho through the provider surface, which is how the vesting dApp in `dapp/frontend` gets its session, its ledger reads, and its submissions.
- Carpincho owns user keys and signs locally.
- The CIP-0103 path: a dApp talks to the wallet through the provider surface, which is how the vesting dApp in `dapp/frontend` gets its session, its ledger reads, and its submissions.
- The wallet owns user keys and signs locally.
- wallet-service holds `CANTON_BACKEND_TOKEN` and remains the external-party onboarding bridge.
- Splice LocalNet owns the app-user participant/validator, Scan, SV, and CC infrastructure.
- Splice and wallet-service share the `canton-barebones` Docker Compose project.
- Carpincho should use generated bearer tokens for direct LocalNet endpoints; it should not copy `CANTON_AUTH_SECRET` into the browser.
- The wallet should use generated bearer tokens for direct LocalNet endpoints; it should not copy `CANTON_AUTH_SECRET` into the browser.

## Services And Ports

| Service | URL / Port | Purpose |
| --- | --- | --- |
| wallet-service | `http://localhost:3010` | Carpincho bridge for onboarding and JSON API calls |
| Carpincho wallet | `http://localhost:3011` | browser wallet UI/provider |
| wallet-service | `http://localhost:3010` | wallet bridge for onboarding and JSON API calls |
| CIP-0103 browser wallet | `http://localhost:3011` | browser wallet UI/provider, run from its own repo |
| dApp frontend | `http://localhost:3012` | example dApp |
| app-user Wallet UI | `http://wallet.localhost:2000` | optional official Splice wallet UI |
| app-user Ledger API | `grpc://localhost:2901` | SDK/tools |
| app-user Admin API | `grpc://localhost:2902` | wallet-service/tools |
| app-user Validator API | `http://localhost:2903` | health/tools |
| app-user JSON API | `http://localhost:2975` | wallet-service/tools |
| app-user Validator proxy | `http://localhost:2000/api/validator` | Carpincho/tools |
| app-user Validator proxy | `http://localhost:2000/api/validator` | wallet/tools |
| app-provider backend APIs | `grpc://localhost:3901`, `grpc://localhost:3902`, `http://localhost:3903`, `http://localhost:3975` | official bundle wiring, unused |
| app-provider UI port | `http://localhost:3000` | exposed by Nginx, routes disabled |
| Scan UI | `http://scan.localhost:4000` | explorer/read model UI |
| Scan API | `http://scan.localhost:4000/api/scan` | Carpincho/tools |
| Scan API | `http://scan.localhost:4000/api/scan` | wallet/tools |
| Amulet Registry | `http://localhost:2000/api/validator/v0/scan-proxy` | token metadata |
| SV UI | `http://sv.localhost:4000` | Super Validator operations UI |
| sv Ledger/Admin/JSON APIs | `grpc://localhost:4901`, `grpc://localhost:4902`, `http://localhost:4975` | Splice internals/tools |
Expand All @@ -83,7 +83,7 @@ State boundaries:

`CANTON_AUTH_AUDIENCE` plus `CANTON_AUTH_SECRET` is the local signing recipe.
`CANTON_BACKEND_TOKEN` is the generated token. The token script defaults the
JWT subject to `ledger-api-user`; Carpincho can use a separate token generated
JWT subject to `ledger-api-user`; the wallet can use a separate token generated
with the same script, configured manually in its LocalNet settings.

## Orchestration
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
"$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"vcs": {
"clientKind": "git",
Expand Down
4 changes: 2 additions & 2 deletions canton-barebones/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ SPLICE_BUNDLE_DIR=${HOME}/.canton-dappbooster/splice-localnet
SPLICE_COMPOSE_PROJECT_NAME=canton-barebones

# LocalNet dev JWT recipe. These values are used only by scripts/mint-token.mjs.
# Do not copy CANTON_AUTH_SECRET into Carpincho.
# Do not copy CANTON_AUTH_SECRET into the wallet.
CANTON_AUTH_AUDIENCE=https://canton.network.global
CANTON_AUTH_SECRET=unsafe

Expand All @@ -24,7 +24,7 @@ CANTON_BACKEND_TOKEN=

# Wallet-service. It is started by pnpm run canton:up after Splice LocalNet.
WALLET_SERVICE_PORT=3010
# Only Carpincho's dev server needs listing. The dApp never calls this service, and the unpacked
# Only the wallet's dev server needs listing. The dApp never calls this service, and an unpacked
# extension is exempt from CORS through its own <all_urls> host_permissions.
WALLET_SERVICE_CORS_ORIGINS=http://localhost:3011
NETWORK=canton:localnet
Expand Down
8 changes: 4 additions & 4 deletions canton-barebones/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ that config in.
Splice and wallet-service share the `canton-barebones` Docker Compose project,
so Docker groups the full local stack together.

`app-user` is Splice's primary local validator name. It is not the Carpincho
user and not a product user.
`app-user` is Splice's primary local validator name. It is not a wallet user
and not a product user.

## Start

Expand Down Expand Up @@ -60,7 +60,7 @@ CANTON_AUTH_SECRET
It prints a JWT. It does not edit `.env`. Pass a subject as the first argument
only if LocalNet expects something other than `ledger-api-user`.

Do not put `CANTON_AUTH_SECRET` in Carpincho. Generate a token and paste only
Do not put `CANTON_AUTH_SECRET` in the wallet. Generate a token and paste only
the token.

## Wallet Service
Expand All @@ -79,7 +79,7 @@ Admin API grpc://host.docker.internal:2902

| Service | What It Is | URL / Port |
| --- | --- | --- |
| wallet-service | Carpincho bridge | `http://localhost:3010` |
| wallet-service | wallet bridge | `http://localhost:3010` |
| app-user Wallet UI | official Splice wallet UI | `http://wallet.localhost:2000` |
| app-user Ledger API | gRPC Ledger API | `grpc://localhost:2901` |
| app-user Admin API | gRPC Admin API | `grpc://localhost:2902` |
Expand Down
2 changes: 1 addition & 1 deletion canton-barebones/scripts/health-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ check_http "Scan UI" "http://scan.localhost:4000"
check_http "SV UI" "http://sv.localhost:4000"

echo ""
echo "Carpincho bridge"
echo "Wallet bridge"
check_http "wallet-service" "http://localhost:${WALLET_SERVICE_PORT:-3010}/health"

echo ""
Expand Down
2 changes: 1 addition & 1 deletion canton-barebones/scripts/mint-token.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const main = () => {
process.stdout.write(`${token}\n\n`)
process.stdout.write('For wallet-service:\n')
process.stdout.write(` CANTON_BACKEND_TOKEN=${token}\n\n`)
process.stdout.write('For Carpincho LocalNet settings:\n')
process.stdout.write('For the wallet LocalNet settings:\n')
process.stdout.write(' Use this token as the LocalNet bearer token.\n')
process.stdout.write(
' You may reuse it for local dev or generate another token with this script.\n',
Expand Down
6 changes: 3 additions & 3 deletions canton-barebones/test/mint-token.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const b64urlDecode = (value) => {
describe('Canton token generation', () => {
it('creates the HS256 JWT Splice LocalNet accepts for local auth', () => {
// Scenario: Splice LocalNet services share the unsafe local JWT recipe.
// The generated token is what operators paste into wallet-service or
// Carpincho dev settings, while the signing secret stays in .env.
// The generated token is what operators paste into wallet-service or the
// wallet's dev settings, while the signing secret stays in .env.
const token = createCantonToken({
subject: 'ledger-api-user',
audience: 'https://canton.network.global',
Expand Down Expand Up @@ -56,6 +56,6 @@ describe('Canton token generation', () => {

assert.match(output, /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/)
assert.match(output, /CANTON_BACKEND_TOKEN=/)
assert.match(output, /Carpincho LocalNet settings/)
assert.match(output, /wallet LocalNet settings/)
})
})
4 changes: 2 additions & 2 deletions canton-barebones/wallet-service/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ This file applies only to `canton-barebones/wallet-service/`. For monorepo-wide

## Scope

The wallet-service is a consumer-dApp-agnostic Express JSON-RPC bridge between Carpincho and the local Canton participant. It holds the Canton bearer token boundary, prepares and executes transactions, proxies participant reads, exposes CIP-56 token-standard reads/transfers and Amulet (Canton Coin) preapproval management and DevNet faucet tap, and handles wallet-internal party onboarding.
The wallet-service is a consumer-dApp-agnostic Express JSON-RPC bridge between a CIP-0103 wallet and the local Canton participant. It holds the Canton bearer token boundary, prepares and executes transactions, proxies participant reads, exposes CIP-56 token-standard reads/transfers and Amulet (Canton Coin) preapproval management and DevNet faucet tap, and handles wallet-internal party onboarding.

## Working Rules

- Keep this service agnostic to the *consumer dApp*. Canton-standard logic is in scope: CIP-56 token-standard reads/transfers and Amulet (Canton Coin) preapproval — including the Splice/Amulet template ids those require. What stays out is consumer-dApp-specific routes, template ids, or command logic (e.g. the `vesting-lite` templates).
- Keep the public dApp-facing API in Carpincho. This service exposes only the HTTP bridge Carpincho needs.
- Keep the public dApp-facing API in the wallet. This service exposes only the HTTP bridge the wallet needs.
- Keep wallet-internal party onboarding under `/admin/party/*`, not on the `/rpc` dApp surface.
- Keep `ledgerApi` as a participant-native pass-through. Do not silently translate request bodies or wrap participant responses.
- Keep token handling inside this service boundary. Do not expose `CANTON_BACKEND_TOKEN` to the dApp or wallet UI.
Expand Down
Loading