diff --git a/AGENTS.md b/AGENTS.md index 641bea56..1f48fb8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,7 +82,6 @@ Subproject docs must not restate root rules. They should describe only their loc - `npm run build-dar -- ` / `npm run deploy-dar -- ` - `npm run carpincho:build:extension` - `npm run app:dev` -- For local-stack convenience, [`scripts/dev-stack.sh`](scripts/dev-stack.sh) wraps the shortcuts above behind an interactive menu (run with no args) or direct subcommands (`install`, `docker-up`, `up`, `down`, `docker-down`, `mock-up`, `mock-down`, `extension`, `status`). The `npm` scripts remain canonical; the helper just orchestrates them. See [`README.md`](README.md). - 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 `package-lock.json` as authoritative. Do not regenerate it as part of unrelated changes, and do not reintroduce per-package lockfiles. - The root `package.json` pins `@canton-network/dapp-sdk` to `1.1.0` via `overrides`: consumers declare `^1.1.0`, but `1.2.0` is intentionally held back. npm 11 does not persist `overrides` into `package-lock.json`, so the pin is enforced by the override on every relock and by the resolved `1.1.0` entry in the lock on every plain install. Do not bump it without testing the dApp flow against the newer SDK. diff --git a/README.md b/README.md index 6fadf8fd..7c8c8503 100644 --- a/README.md +++ b/README.md @@ -1,135 +1,166 @@ # Canton dApp Booster -Minimal local stack: +Local Canton Network stack for wallet-first dApp experiments. ```mermaid flowchart TD fe["dapp/frontend
dApp frontend
http://localhost:3012"] wallet["carpincho-wallet
Vault + signer
http://localhost:3011"] - ws["canton-barebones/wallet-service
Canton bridge
http://localhost:3010"] - cb["canton-barebones
Participant JSON API http://localhost:3013
Ledger/Admin gRPC localhost:3014 / 3015"] - dar["dapp/daml
quickstart-tally DAR
.daml/dist/*.dar"] + ws["canton-barebones/wallet-service
External-party bridge
http://localhost:3010"] + au["Splice app-user
primary local validator
JSON API http://localhost:2975"] + sv["Splice sv
SV / DSO / synchronizer side"] + scan["Scan
Splice read model
http://scan.localhost:4000"] + dar["dapp/daml
quickstart-tally DAR"] fe <-->|"Injected CIP-0103 provider
optional WalletConnect"| wallet - wallet -->|"JSON-RPC /rpc
prepare, execute, read, onboard"| ws - ws -->|"Canton JSON API
self-minted JWT"| cb - dar -->|"deploy DAR package"| cb + wallet -->|"external-party onboarding"| ws + wallet -->|"Scan API / token metadata"| scan + ws -->|"Bearer CANTON_BACKEND_TOKEN"| au + au <--> sv + sv -->|"indexed Splice read model"| scan + dar -->|"deploy package"| au ``` -The dApp frontend knows the Tally DAML signature and talks to Carpincho through the injected CIP-0103 browser provider. Carpincho owns the local signing key and uses the wallet service to prepare, read, and execute against the Canton participant. WalletConnect remains available as an optional fallback path. +`canton:up` activates the official Splice LocalNet `sv` and `app-user` Docker +profiles, then starts wallet-service. It does not start Keycloak or OIDC. +The app-provider UI containers are not started; a local compose override +disables their Nginx routes. The official shared Canton/Splice containers still +expose app-provider backend ports because the bundle bakes 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 technical name for the primary local validator; it is not +the Carpincho user. ## Installation Prerequisites: - Node.js 24 -- npm -- Docker +- npm `>=7` +- Docker with about 8 GB memory available - `dpm` on `PATH` (DAML SDK 3.4.11), required for building DARs -### Recommended +Install workspace dependencies: ```bash -npx dappbooster --canton +npm install ``` -### Manual +Create the local env file: ```bash -npm install +cp canton-barebones/.env.example canton-barebones/.env ``` -### Environment files - -#### Mandatory +Generate the backend token and paste the printed `CANTON_BACKEND_TOKEN=...` +line into `canton-barebones/.env`: ```bash -cp canton-barebones/.env.example canton-barebones/.env +npm run canton:token -- ledger-api-user ``` -#### Optional +Token configuration: + +| Name | What It Is | Who Uses It | +| --- | --- | --- | +| `CANTON_AUTH_AUDIENCE` | JWT audience recipe value | token script | +| `CANTON_AUTH_SECRET` | local unsafe JWT signing secret | token script only | +| `CANTON_BACKEND_TOKEN` | generated JWT pasted into `.env` | wallet-service | +| Carpincho LocalNet token | generated JWT pasted into Carpincho settings | Carpincho | + +The token script uses `ledger-api-user` as the default JWT subject. Generate +another token with the same script or reuse the backend token locally. Do not +copy `CANTON_AUTH_SECRET` into Carpincho. -Only for the WalletConnect fallback. Copy each and set `VITE_WC_PROJECT_ID` (see the dApp frontend [WalletConnect setup](dapp/frontend/README.md#walletconnect-fallback)): +Optional WalletConnect fallback: ```bash cp carpincho-wallet/.env.local.example carpincho-wallet/.env.local cp dapp/frontend/.env.local.example dapp/frontend/.env.local ``` -## Dev stack script +Set `VITE_WC_PROJECT_ID` in both files only if you use WalletConnect. -[`scripts/dev-stack.sh`](scripts/dev-stack.sh) automates the manual Quick Start below. Run it with no arguments for an interactive menu. +## Quick Start + +Start the stack: ```bash -./scripts/dev-stack.sh +npm run canton:up +npm run canton:health ``` -Or call an action directly: +Build and deploy the sample DAR: ```bash -./scripts/dev-stack.sh +npm run build-dar -- dapp/daml +npm run deploy-dar -- dapp/daml/.daml/dist/quickstart-tally-0.0.1.dar ``` -| Menu item | Action | What it does | -|-----------|--------|--------------| -| Install | `install` | Install and link every workspace from the repo root (`npm install`). | -| Docker up | `docker-up` | Launch Docker Desktop (macOS only). | -| Docker down | `docker-down` | Quit Docker Desktop (macOS only). | -| Stack up | `up` | Bring up containers, build + deploy the DAR, start the wallet and dApp dev servers, build the extension. | -| Stack down | `down` | Stop the dev servers and tear down the containers. | -| Wallet up | `mock-up` | Start the mocked wallet-service + Carpincho web app with no Docker. | -| Wallet down | `mock-down` | Stop the mocked wallet-service + Carpincho web app. | -| Build extension | `extension` | Build the Chrome extension and copy it to your desktop. | -| (CLI only) | `status` | Show running containers and listening ports. | - -Notes: +Verify wallet-service: -- Docker lifecycle is managed separately from the stack: `up` and `down` assume Docker is already running and never start or quit it. Start/quit Docker with `docker-up` / `docker-down`, the Docker app, or your own CLI. -- `up` requires Docker running and `dpm` on `PATH` (for the DAR build). - -## Quick Start (manual) +```bash +npm run wallet-service:health +``` -1. **Start Canton + wallet-service** ([`canton-barebones`](canton-barebones/README.md)): +Start Carpincho and the dApp: - ```bash - npm run canton:up - npm run canton:health - ``` +```bash +npm run wallet:dev +npm run app:dev +``` -2. **Build and deploy the Tally DAR** ([`dapp/daml`](dapp/daml/README.md) builds, [`canton-barebones`](canton-barebones/README.md#deploy-a-dar) deploys): +Open the dApp: - ```bash - npm run build-dar -- dapp/daml - npm run deploy-dar -- dapp/daml/.daml/dist/quickstart-tally-0.0.1.dar - ``` +```text +http://localhost:3012 +``` -3. **Build and load the Carpincho extension** ([`carpincho-wallet`](carpincho-wallet/README.md#browser-extension)): +In the frontend: - ```bash - npm run carpincho:build:extension - ``` +1. Keep `canton:localnet` in settings. +2. Click `Connect with Carpincho`. +3. Approve the request in Carpincho. -4. **Start the dApp frontend** ([`dapp/frontend`](dapp/frontend/README.md)): +## Extension - ```bash - npm run app:dev - ``` - -For host-side iteration without Docker, see the wallet-service [mock mode](canton-barebones/wallet-service/README.md#mock-mode). For the WalletConnect fallback, see the dApp frontend [WalletConnect setup](dapp/frontend/README.md#walletconnect-fallback). +Build the extension: -## Ports +```bash +npm run carpincho:build:extension +``` -| Component | URL / Port | -| --------------------------- | ----------------------- | -| Wallet service | `http://localhost:3010` | -| Carpincho wallet | `http://localhost:3011` | -| dApp frontend | `http://localhost:3012` | -| Canton JSON API | `http://localhost:3013` | -| Canton Ledger API | `grpc://localhost:3014` | -| Canton Admin API | `grpc://localhost:3015` | -| Canton health | `http://localhost:3016` | -| Canton sequencer public API | `localhost:3017` | -| Canton Postgres | `localhost:3018` | +Load `carpincho-wallet/dist-extension` from `chrome://extensions` with +Developer mode enabled. + +## Services And Ports + +| Service | What It Is | URL / Port | Who Uses It | +| --- | --- | --- | --- | +| wallet-service | Carpincho bridge for external-party onboarding | `http://localhost:3010` | Carpincho | +| Carpincho wallet | Browser wallet UI/provider | `http://localhost:3011` | user/dApp | +| dApp frontend | Example dApp | `http://localhost:3012` | user | +| app-user Wallet UI | Official Splice wallet UI for app-user | `http://wallet.localhost:2000` | optional/manual | +| app-user Ledger API | gRPC Ledger API | `grpc://localhost:2901` | SDK/tools | +| app-user Admin API | gRPC Admin API | `grpc://localhost:2902` | wallet-service/tools | +| app-user Validator API | Splice validator readiness/API | `http://localhost:2903` | health/tools | +| app-user JSON API | JSON Ledger API | `http://localhost:2975` | wallet-service/tools | +| app-user Validator proxy | wallet-sdk validator route | `http://localhost:2000/api/validator` | Carpincho | +| app-provider backend APIs | Official bundle backend wiring, unused here | `grpc://localhost:3901`, `grpc://localhost:3902`, `http://localhost:3903`, `http://localhost:3975` | not used | +| app-provider UI port | Nginx port exposed by the bundle; routes disabled here | `http://localhost:3000` | not used | +| Scan UI | Splice explorer/read model UI | `http://scan.localhost:4000` | optional/manual | +| Scan API | Splice indexed API | `http://scan.localhost:4000/api/scan` | Carpincho/tools | +| Amulet Registry | token metadata via scan proxy | `http://localhost:2000/api/validator/v0/scan-proxy` | Carpincho/tools | +| SV UI | Super Validator operations UI | `http://sv.localhost:4000` | optional/manual | +| sv Ledger/Admin/JSON APIs | Official SV participant APIs | `grpc://localhost:4901`, `grpc://localhost:4902`, `http://localhost:4975` | Splice internals/tools | +| sv Validator API | SV readiness/admin surface | `http://localhost:4903` | health checks | +| PostgreSQL | Splice LocalNet DB | `localhost:5432` | LocalNet containers/tools | + +If `wallet.localhost`, `scan.localhost`, or `sv.localhost` do not resolve, add: + +```text +127.0.0.1 wallet.localhost scan.localhost sv.localhost +``` ## Releasing @@ -149,4 +180,4 @@ The root `package.json` `version` is the single source of truth for the release. git push --follow-tags ``` -3. Publish a GitHub Release for that tag (the GitHub UI, or `gh release create v`). \ No newline at end of file +3. Publish a GitHub Release for that tag (the GitHub UI, or `gh release create v`). diff --git a/architecture.md b/architecture.md index ef5def2a..f32e738d 100644 --- a/architecture.md +++ b/architecture.md @@ -1,125 +1,96 @@ # Architecture Overview — Canton dApp Booster - +## Tech Stack -## Tech Stack (per subproject) - -| Subproject | Stack | Purpose | -|------------|-------|---------| -| `canton-barebones/` | Docker Compose + Bash + Node scripts | Local Canton participant node, Postgres, mint-token helper, DAR deploy script, health-check | -| `dapp/daml/` | DAML (`dpm` build) | `quickstart-tally` model — DAR consumed by Canton | -| `canton-barebones/wallet-service/` | Node 24 + Express 5 + TypeScript + `@canton-network/wallet-sdk` | JSON-RPC bridge between the wallet and the Canton participant JSON API. Started by `npm run canton:up` as a docker-compose service. Self-mints its Canton JWT at boot from `CANTON_AUTH_AUDIENCE` / `CANTON_AUTH_SECRET`. `WALLET_SERVICE_MOCK=1` (`src/mock.ts`) short-circuits the dispatcher with canned responses. | -| `carpincho-wallet/` | Vite 6 + React 18 + Tailwind v4 + Radix UI + Biome + WalletConnect Sign Client 2.x + `@noble/ed25519` | CIP-0103 wallet (web + Chrome extension), encrypted local vault, signing, injected provider, optional WalletConnect | -| `dapp/frontend/` | Vite + React + `@canton-network/dapp-sdk` + Tailwind v4 + Radix UI + Biome | dApp UI that talks to the wallet through the injected CIP-0103 provider, with optional WalletConnect fallback | -| `dapp/e2e/` | Playwright + TypeScript | Black-box integration tests for the dApp, Carpincho, wallet-service, and Canton stack | - -## Project Structure - -``` -. -├── .claude/ -│ ├── settings.local.json (gitignored) -│ └── skills/{create-pr,issue} Agent helper skills -├── .github/ -│ ├── ISSUE_TEMPLATE/ bug / feature / epic / spike templates -│ ├── PULL_REQUEST_TEMPLATE.md -│ └── workflows/ release pipeline (build + attach extension zip) -├── .husky/ commit-msg, pre-commit, pre-push hooks -├── canton-barebones/ Local Canton participant + Postgres + wallet-service -├── canton-connect-kit/ React hooks for CIP-0103 wallet connections -├── carpincho-wallet/ CIP-0103 wallet (web + Chrome extension) -├── dapp/ -│ ├── daml/ quickstart-tally DAML model -│ ├── frontend/ dApp UI -│ └── e2e/ Black-box integration tests -├── AGENTS.md Agent rules — monorepo-wide -├── CLAUDE.md Compatibility shim pointing to AGENTS.md -├── architecture.md THIS FILE -├── README.md Bring-up runbook for the local stack -├── commitlint.config.js Conventional Commit enforcement -├── .lintstagedrc.mjs Per-subproject lint dispatch -├── .nvmrc Node 24 -└── package.json npm workspaces root + orchestration scripts (npm --prefix ) -``` +| --- | --- | --- | +| `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 | +| `carpincho-wallet/` | Vite 6 + React 18 + Tailwind v4 + Radix UI + WalletConnect + `@tanstack/react-query` + `@noble/ed25519` | CIP-0103 browser wallet, encrypted vault, signer, injected provider | +| `dapp/frontend/` | Vite + React + `@canton-network/dapp-sdk` | Example dApp that talks to Carpincho | +| `dapp/daml/` | DAML | `quickstart-tally` DAR | +| `dapp/e2e/` | Playwright + TypeScript | Black-box integration tests | +| `canton-connect-kit/` | TypeScript + React | Reusable wallet connection hooks | ## Data Flow -The whole local stack is one signing loop. The dApp frontend discovers Carpincho through the injected CIP-0103 browser provider; Carpincho signs locally and routes the signed transaction through the wallet-service JSON-RPC bridge, which calls the Canton participant's JSON API; the participant materialises the change against the deployed `quickstart-tally` DAR. WalletConnect remains available as an opt-in fallback path. - ```mermaid flowchart TD - fe["dapp/frontend
dApp frontend
http://localhost:3012"] - wallet["carpincho-wallet
Vault + signer
http://localhost:3011"] - ws["canton-barebones/wallet-service
Canton bridge
http://localhost:3010"] - cb["canton-barebones
Participant JSON API http://localhost:3013
Ledger/Admin gRPC localhost:3014 / 3015"] - dar["dapp/daml
quickstart-tally DAR
.daml/dist/*.dar"] - - fe <-->|"Injected CIP-0103 provider
optional WalletConnect"| wallet - wallet -->|"JSON-RPC /rpc
prepare, execute, read, onboard"| ws - ws -->|"Canton JSON API
Bearer CANTON_BACKEND_TOKEN"| cb - dar -->|"deploy DAR package"| cb + fe["dapp/frontend
http://localhost:3012"] + wallet["carpincho-wallet
http://localhost:3011"] + ws["wallet-service
http://localhost:3010"] + au["Splice app-user
JSON API http://localhost:2975"] + sv["Splice sv
DSO / synchronizer side"] + scan["Scan
http://scan.localhost:4000"] + dar["dapp/daml DAR"] + + fe <-->|"CIP-0103 provider"| wallet + wallet -->|"external-party onboarding"| ws + ws -->|"CANTON_BACKEND_TOKEN"| au + au <--> sv + dar --> au ``` -State boundaries: - -- `dapp/frontend` never touches the participant directly. It only knows about Carpincho through the injected CIP-0103 provider (or optional WalletConnect fallback) and the dApp's DAML signature. -- `carpincho-wallet` holds all signing keys (PBKDF2 + AES-GCM vault, Ed25519). It never talks to Canton directly; it goes through `canton-barebones/wallet-service`. -- `canton-barebones/wallet-service` is the only component holding the Canton bearer token. It self-mints the token at boot, then validates and forwards JSON-RPC calls onto the participant's JSON API. -- `canton-barebones` is the participant. Its bearer-token validation pins the trust boundary. - -## Service Ports +`app-user` is the primary local validator from the official Splice LocalNet +bundle. It is not a product user. `sv` provides the Super Validator / DSO side +needed by Splice and Canton Coin. The app-provider UI profile is not started; +its Nginx routes are disabled locally. The official shared Canton/Splice +containers still expose app-provider backend ports. -Local ports are intentionally assigned in the `3010+` range so they collide with nothing else on a dev machine. - -| Component | URL / Port | -|-----------|------------| -| Wallet service | `http://localhost:3010` | -| Carpincho wallet | `http://localhost:3011` | -| dApp frontend | `http://localhost:3012` | -| Canton JSON API | `http://localhost:3013` | -| Canton Ledger API | `grpc://localhost:3014` | -| Canton Admin API | `grpc://localhost:3015` | -| Canton health | `http://localhost:3016` | -| Canton sequencer public API | `localhost:3017` | -| Canton Postgres | `localhost:3018` | +State boundaries: -## Environment Variables +- The dApp talks to Carpincho through the CIP-0103 provider surface. +- Carpincho 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. + +## 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 | +| 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-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 | +| 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 | +| sv Validator API | `http://localhost:4903` | health checks | +| PostgreSQL | `localhost:5432` | Splice LocalNet database | + +## Auth | Variable | Owner | Purpose | -|----------|-------|---------| -| `VITE_WC_PROJECT_ID` | `carpincho-wallet/.env.local`, `dapp/frontend/.env.local` | Optional WalletConnect / Reown project ID. Same value in both subprojects when using the WalletConnect fallback; not required for the injected extension provider path. | -| `CANTON_BACKEND_TOKEN` | `canton-barebones/wallet-service` runtime env (compose) | Self-minted at boot from `CANTON_AUTH_AUDIENCE` / `CANTON_AUTH_SECRET` / `CANTON_ADMIN_USER_ID`. Set explicitly (e.g. via the compose env override) to bypass self-mint. Mint ad-hoc with `npm run canton:token`. | - -Runtime-only configuration that varies between sessions (RPC URL, Canton network name, Carpincho URL) is stored in `localStorage` inside the wallet and the frontend, configured from each UI — not from env files. +| --- | --- | --- | +| `CANTON_AUTH_AUDIENCE` | `canton-barebones/.env` | JWT audience recipe used by `npm run canton:token` | +| `CANTON_AUTH_SECRET` | `canton-barebones/.env` | unsafe local signing secret used only by the token script | +| `CANTON_BACKEND_TOKEN` | `canton-barebones/.env` | generated JWT consumed by wallet-service | -## Orchestration Scripts +`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 +with the same script, configured manually in its LocalNet settings. -Driven from root `package.json`: +## Orchestration | Command | What it does | -|---------|--------------| -| `npm run canton:up` / `canton:down` | docker compose up/down inside `canton-barebones/` | -| `npm run canton:health` | Hit the participant health endpoint at `:3016` | -| `npm run canton:token` | Mint a dev JWT for the wallet-service user | -| `npm run build-dar -- ` | DAML build via `dpm` inside the provided DAML project directory | -| `npm run deploy-dar -- ` | Deploy the provided DAR to the local participant | -| `npm --prefix canton-barebones/wallet-service run dev` | Host-side dev mode for the wallet-service on `:3010` (tsx watch). The container-side service is started by `canton:up`. | -| `npm run wallet-service:health` | Hit the wallet-service health endpoint at `:3010` | -| `npm --prefix canton-barebones run wallet-service:logs` | Tail the wallet-service container's logs | -| `npm run carpincho:build:extension` | Build the Chrome extension into `carpincho-wallet/dist-extension` | -| `npm run app:dev` | Start the dApp frontend on `:3012` (Vite, strict port) | - -For the full bring-up sequence, follow [`README.md`](README.md). - -## Further Reading - -- [`carpincho-wallet/architecture.md`](carpincho-wallet/architecture.md) — wallet-internal architecture: Vault crypto, CIP-0103 dispatcher, WalletConnect integration, Chrome extension bridge, theming, auth/session -- [`canton-connect-kit/architecture.md`](canton-connect-kit/architecture.md) — connect-kit internals: provider context, connectors, hooks, event bridge -- [`canton-barebones/README.md`](canton-barebones/README.md) — local participant setup -- [`dapp/daml/README.md`](dapp/daml/README.md) — DAML model -- [`canton-barebones/wallet-service/README.md`](canton-barebones/wallet-service/README.md) — JSON-RPC bridge -- [`dapp/frontend/README.md`](dapp/frontend/README.md) — dApp UI -- [`dapp/e2e/README.md`](dapp/e2e/README.md) — black-box integration tests +| --- | --- | +| `npm run canton:up` | download/cache Splice bundle, start `sv + app-user` UI profiles, then wallet-service | +| `npm run canton:down` | stop wallet-service and Splice LocalNet, preserving volumes | +| `npm run canton:health` | check app-user, sv, Scan, wallet UI, and wallet-service | +| `npm run canton:token -- ledger-api-user` | generate a LocalNet dev JWT | +| `npm run deploy-dar -- ` | upload DAR to app-user JSON API | +| `npm run wallet:dev` | start Carpincho web UI | +| `npm run app:dev` | start the dApp frontend | + +For the bring-up sequence, follow [`README.md`](README.md). diff --git a/canton-barebones/.env.example b/canton-barebones/.env.example index c39a799a..6d364944 100644 --- a/canton-barebones/.env.example +++ b/canton-barebones/.env.example @@ -1,39 +1,31 @@ -# Canton barebones dev values. Copy to .env and edit if ports conflict. +# Canton dApp Booster local Splice stack. Copy to .env before starting. COMPOSE_PROJECT_NAME=canton-barebones -# Images -CANTON_IMAGE=ghcr.io/digital-asset/decentralized-canton-sync/docker/canton:0.5.3 -POSTGRES_IMAGE=postgres:14 +# Official Splice LocalNet bundle. This stack activates sv + app-user UI +# profiles and disables app-provider nginx routes. The shared backend +# containers still expose app-provider ports because the bundle bakes them in. +SPLICE_IMAGE_TAG=0.5.18 +SPLICE_BUNDLE_DIR=${HOME}/.canton-dappbooster/splice-localnet +# Defaults to COMPOSE_PROJECT_NAME so Docker groups Splice and wallet-service together. +# Override only when intentionally running Splice as a separate compose project. +SPLICE_COMPOSE_PROJECT_NAME=canton-barebones -# Postgres -POSTGRES_HOST_PORT=3018 -POSTGRES_USER=canton -POSTGRES_PASSWORD=canton -POSTGRES_DB=postgres - -# Canton public ports on the host -CANTON_LEDGER_API_PORT=3014 -CANTON_ADMIN_API_PORT=3015 -CANTON_JSON_API_PORT=3013 -CANTON_SEQUENCER_PUBLIC_PORT=3017 -CANTON_HTTP_HEALTH_PORT=3016 - -# Canton internal auth for local/dev. -# The participant accepts HS256 JWTs with this audience/secret. -CANTON_AUTH_AUDIENCE=https://canton-barebones.local +# LocalNet dev JWT recipe. These values are used only by scripts/mint-token.mjs. +# Do not copy CANTON_AUTH_SECRET into Carpincho. +CANTON_AUTH_AUDIENCE=https://canton.network.global CANTON_AUTH_SECRET=unsafe -CANTON_ADMIN_USER_ID=wallet-service -# Convenience token generated from the values above: -# sub = CANTON_ADMIN_USER_ID, aud = CANTON_AUTH_AUDIENCE, alg = HS256, secret = CANTON_AUTH_SECRET. -# Regenerate with: scripts/mint-token.sh wallet-service -CANTON_ADMIN_TOKEN=mock-token-replace-with-scripts-mint-token-output +# Required for wallet-service real mode. +# Generate with: +# npm run canton:token -- ledger-api-user +# Then paste the generated CANTON_BACKEND_TOKEN line here. +CANTON_BACKEND_TOKEN= -# Wallet-service (started by docker compose alongside Canton) +# Wallet-service. It is started by npm run canton:up after Splice LocalNet. WALLET_SERVICE_PORT=3010 WALLET_SERVICE_CORS_ORIGINS=http://localhost:3011 -NETWORK=canton:local +NETWORK=canton:localnet WALLET_PROVIDER_ID=wallet-service WALLET_PROVIDER_VERSION=0.1.0 WALLET_PROVIDER_URL=http://localhost:3010 diff --git a/canton-barebones/README.md b/canton-barebones/README.md index e9e8015e..73216c71 100644 --- a/canton-barebones/README.md +++ b/canton-barebones/README.md @@ -1,36 +1,109 @@ # Canton Barebones -Minimal local Canton barebones for wallet-first app experiments. +Local Splice LocalNet wrapper for Canton dApp Booster. -## Start +This package starts the official Splice LocalNet bundle with: + +```text +sv +app-user +wallet-service +``` + +It does not start: + +```text +Keycloak/OIDC +``` + +It also does not start the app-provider UI containers. A local compose override +disables app-provider Nginx routes. The official shared Canton/Splice +containers still expose app-provider backend ports because the bundle bakes +that config in. -Run only the local Canton participant, Postgres, and wallet-service: +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. + +## Start ```bash cp .env.example .env -docker compose up -d -./scripts/health-check.sh +npm run token -- ledger-api-user ``` -## Wallet service +Paste the printed `CANTON_BACKEND_TOKEN=...` line into `.env`, then run: -`docker compose up -d` (via `npm run canton:up`) brings up `wallet-service` alongside postgres + canton. Verify with `npm run wallet-service:health`. +```bash +npm run up +npm run health +``` -The wallet-service self-mints an HS256 JWT at boot from `CANTON_AUTH_AUDIENCE`, `CANTON_AUTH_SECRET`, and `CANTON_ADMIN_USER_ID`. +From the repo root, use: -Set `WALLET_SERVICE_MOCK=1` in `.env` to short-circuit Canton calls; the service still starts but every `/rpc` method returns canned mock data. +```bash +npm run canton:up +npm run canton:health +``` + +## Auth + +The token script reads: + +```text +CANTON_AUTH_AUDIENCE +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`. -Details: +Do not put `CANTON_AUTH_SECRET` in Carpincho. Generate a token and paste only +the token. -- [wallet-service token](wallet-service/README.md#token) -- [wallet-service mock mode](wallet-service/README.md#mock-mode) +## Wallet Service -## Auth Config +`npm run up` starts `wallet-service` after app-user is ready. -The participant accepts local/dev HS256 JWTs configured by: +wallet-service points to app-user: -- `.env`: `CANTON_AUTH_AUDIENCE`, `CANTON_AUTH_SECRET`, `CANTON_ADMIN_USER_ID` -- [`config/canton/app.conf`](config/canton/app.conf): Canton ledger API auth service +```text +JSON API http://host.docker.internal:2975 +Ledger API grpc://host.docker.internal:2901 +Admin API grpc://host.docker.internal:2902 +``` + +Set `WALLET_SERVICE_MOCK=1` in `.env` to short-circuit Canton calls. Mock mode +does not require `CANTON_BACKEND_TOKEN`. + +## Services And Ports + +| Service | What It Is | URL / Port | +| --- | --- | --- | +| wallet-service | Carpincho 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` | +| app-user Validator API | Splice validator API | `http://localhost:2903` | +| app-user JSON API | JSON Ledger API | `http://localhost:2975` | +| app-user Validator proxy | wallet-sdk validator route | `http://localhost:2000/api/validator` | +| app-provider backend APIs | official bundle backend wiring, unused here | `grpc://localhost:3901`, `grpc://localhost:3902`, `http://localhost:3903`, `http://localhost:3975` | +| app-provider UI port | Nginx port exposed by the bundle; routes disabled here | `http://localhost:3000` | +| Scan UI | Splice explorer/read model | `http://scan.localhost:4000` | +| Scan API | indexed Splice API | `http://scan.localhost:4000/api/scan` | +| Amulet Registry | token metadata via scan proxy | `http://localhost:2000/api/validator/v0/scan-proxy` | +| SV UI | Super Validator operations UI | `http://sv.localhost:4000` | +| sv Ledger/Admin/JSON APIs | official SV participant APIs | `grpc://localhost:4901`, `grpc://localhost:4902`, `http://localhost:4975` | +| sv Validator API | SV readiness surface | `http://localhost:4903` | +| PostgreSQL | Splice LocalNet DB | `localhost:5432` | + +If hostnames do not resolve, add: + +```text +127.0.0.1 wallet.localhost scan.localhost sv.localhost +``` ## Deploy a DAR @@ -55,4 +128,10 @@ Or call the upload script directly: ./scripts/deploy-dar.sh /path/to/app.dar ``` +The script uploads to app-user: + +```text +http://localhost:2975/v2/packages +``` + `npm run canton:health` must return OK before deploying; otherwise the DAR upload can fail. diff --git a/canton-barebones/config/canton/app.conf b/canton-barebones/config/canton/app.conf deleted file mode 100644 index c157a516..00000000 --- a/canton-barebones/config/canton/app.conf +++ /dev/null @@ -1,96 +0,0 @@ -_storage { - type = postgres - config { - dataSourceClass = "org.postgresql.ds.PGSimpleDataSource" - properties = { - serverName = ${DB_SERVER} - portNumber = 5432 - user = ${DB_USER} - password = ${DB_PASSWORD} - tcpKeepAlive = true - } - } - parameters { - max-connections = 16 - migrate-and-start = true - } -} - -canton { - features { - enable-preview-commands = yes - enable-testing-commands = yes - } - - parameters { - non-standard-config = yes - } - - sequencers { - sequencer1 { - storage = ${_storage} { - config.properties.databaseName = canton_sequencer - } - public-api { - address = "0.0.0.0" - port = 5001 - } - admin-api { - address = "0.0.0.0" - port = 5002 - } - sequencer.type = BFT - } - } - - mediators { - mediator1 { - storage = ${_storage} { - config.properties.databaseName = canton_mediator - } - admin-api { - address = "0.0.0.0" - port = 5202 - } - } - } - - participants { - participant1 { - storage = ${_storage} { - config.properties.databaseName = canton_participant - } - - admin-api { - address = "0.0.0.0" - port = 5012 - } - - ledger-api { - address = "0.0.0.0" - port = 5011 - max-token-lifetime = Inf - admin-token-config.admin-claim = true - auth-services = [{ - type = unsafe-jwt-hmac-256 - target-audience = ${CANTON_AUTH_AUDIENCE} - secret = ${CANTON_AUTH_SECRET} - }] - user-management-service.additional-admin-user-id = ${CANTON_ADMIN_USER_ID} - interactive-submission-service { - enable-verbose-hashing = true - } - } - - http-ledger-api { - address = "0.0.0.0" - port = 7575 - } - - monitoring.http-health-server { - address = "0.0.0.0" - port = 7000 - } - } - } -} diff --git a/canton-barebones/config/canton/bootstrap.canton b/canton-barebones/config/canton/bootstrap.canton deleted file mode 100644 index ec778fa8..00000000 --- a/canton-barebones/config/canton/bootstrap.canton +++ /dev/null @@ -1,15 +0,0 @@ -try { - bootstrap.synchronizer_local() -} catch { - case _: Throwable => println("local synchronizer already initialized; skipping bootstrap") -} - -try { - participant1.synchronizers.connect_local(sequencer1, alias = "local") -} catch { - case _: Throwable => println("participant already connected to local synchronizer; skipping connect") -} - -utils.retry_until_true { - participant1.synchronizers.active("local") -} diff --git a/canton-barebones/config/postgres/init.sql b/canton-barebones/config/postgres/init.sql deleted file mode 100644 index 56cc4b1a..00000000 --- a/canton-barebones/config/postgres/init.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE DATABASE canton_participant; -CREATE DATABASE canton_sequencer; -CREATE DATABASE canton_mediator; diff --git a/canton-barebones/config/splice/localnet-overrides.yaml b/canton-barebones/config/splice/localnet-overrides.yaml new file mode 100644 index 00000000..0909026c --- /dev/null +++ b/canton-barebones/config/splice/localnet-overrides.yaml @@ -0,0 +1,4 @@ +services: + nginx: + volumes: + - ${CANTON_BAREBONES_DIR}/config/splice/nginx-app-provider-disabled.conf.template:/etc/nginx/templates/app-provider.conf.template:ro diff --git a/canton-barebones/config/splice/nginx-app-provider-disabled.conf.template b/canton-barebones/config/splice/nginx-app-provider-disabled.conf.template new file mode 100644 index 00000000..4d437aa3 --- /dev/null +++ b/canton-barebones/config/splice/nginx-app-provider-disabled.conf.template @@ -0,0 +1 @@ +# app-provider UI containers are not part of this local stack. diff --git a/canton-barebones/docker-compose.yaml b/canton-barebones/docker-compose.yaml index a233c3ae..7c094c3a 100644 --- a/canton-barebones/docker-compose.yaml +++ b/canton-barebones/docker-compose.yaml @@ -1,98 +1,26 @@ services: - postgres: - image: "${POSTGRES_IMAGE}" - container_name: canton-barebones-postgres - environment: - POSTGRES_USER: "${POSTGRES_USER}" - POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" - POSTGRES_DB: "${POSTGRES_DB}" - ports: - - "${POSTGRES_HOST_PORT}:5432" - volumes: - - postgres-data:/var/lib/postgresql/data - - ./config/postgres/init.sql:/docker-entrypoint-initdb.d/001-init.sql:ro - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 5s - timeout: 3s - retries: 20 - - canton: - image: "${CANTON_IMAGE}" - container_name: canton-barebones-canton - environment: - DB_SERVER: postgres - DB_USER: "${POSTGRES_USER}" - DB_PASSWORD: "${POSTGRES_PASSWORD}" - CANTON_AUTH_AUDIENCE: "${CANTON_AUTH_AUDIENCE}" - CANTON_AUTH_SECRET: "${CANTON_AUTH_SECRET}" - CANTON_ADMIN_USER_ID: "${CANTON_ADMIN_USER_ID}" - LOG_LEVEL_STDOUT: INFO - LOG_LEVEL_CANTON: INFO - ports: - - "${CANTON_LEDGER_API_PORT}:5011" - - "${CANTON_ADMIN_API_PORT}:5012" - - "${CANTON_JSON_API_PORT}:7575" - - "${CANTON_SEQUENCER_PUBLIC_PORT}:5001" - - "${CANTON_HTTP_HEALTH_PORT}:7000" - volumes: - - ./config/canton/app.conf:/app/app.conf:ro - - ./config/canton/bootstrap.canton:/app/bootstrap.canton:ro - - ./dars:/dars - entrypoint: ["/app/bin/canton"] - command: - - daemon - - --no-tty - - --log-encoder=json - - --log-level-stdout=INFO - - --log-level-canton=INFO - - --log-file-appender=off - - --config - - /app/app.conf - - --bootstrap - - /app/bootstrap.canton - depends_on: - postgres: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "bash -c '/dev/null || exit 1"] - interval: 5s - timeout: 3s - retries: 20 - start_period: 5s - -volumes: - postgres-data: diff --git a/canton-barebones/package.json b/canton-barebones/package.json index a322848a..9188123e 100644 --- a/canton-barebones/package.json +++ b/canton-barebones/package.json @@ -3,10 +3,10 @@ "private": true, "version": "0.1.0", "scripts": { - "up": "docker compose up -d --build", - "down": "docker compose down", + "up": "./scripts/up.sh", + "down": "./scripts/down.sh", "health": "./scripts/health-check.sh", - "token": "node ./scripts/mint-token.mjs wallet-service", + "token": "node ./scripts/mint-token.mjs", "lint": "biome check", "lint:fix": "biome check --write", "format": "biome format --write", diff --git a/canton-barebones/scripts/deploy-dar.sh b/canton-barebones/scripts/deploy-dar.sh index ba55ae94..c2c1032d 100755 --- a/canton-barebones/scripts/deploy-dar.sh +++ b/canton-barebones/scripts/deploy-dar.sh @@ -7,6 +7,14 @@ if [ "$#" -ne 1 ]; then fi ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ -f "$ROOT/.env" ]; then + set -a + # shellcheck disable=SC1091 + source "$ROOT/.env" + set +a +fi + DAR_PATH="$1" if [ ! -f "$DAR_PATH" ]; then @@ -14,18 +22,34 @@ if [ ! -f "$DAR_PATH" ]; then exit 1 fi -mkdir -p "$ROOT/dars" -DAR_NAME="$(basename "$DAR_PATH")" -cp "$DAR_PATH" "$ROOT/dars/$DAR_NAME" - -docker compose --project-directory "$ROOT" exec -T canton /app/bin/canton \ - run /dev/stdin \ - --no-tty \ - -C canton.remote-participants.participant1.ledger-api.address=127.0.0.1,canton.remote-participants.participant1.ledger-api.port=5011,canton.remote-participants.participant1.admin-api.address=127.0.0.1,canton.remote-participants.participant1.admin-api.port=5012 <