Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
c4e76b3
build: vendor the canton-token-forge and vesting DARs
lmcorbalan Sep 4, 2026
0b32a1c
Merge pull request #158 from BootNodeDev/feat/149-vendor-dars
lmcorbalan Sep 4, 2026
99d1213
build: add the canton-token-forge registry service as a dev dependency
lmcorbalan Sep 4, 2026
47406b8
Merge pull request #159 from BootNodeDev/feat/150-registry-dependency
lmcorbalan Sep 4, 2026
10538e7
feat(scripts): seed the DBT instrument admin and config in bootstrap
lmcorbalan Sep 4, 2026
385c1f5
test(scripts): pin the registry env block's exact shape and fix CLAUD…
lmcorbalan Sep 4, 2026
189ef5e
docs: scope the bootstrap bullet's cannot-start-without claim to the …
lmcorbalan Sep 4, 2026
81401da
fix(scripts): preflight the token-forge package and fix bootstrap's m…
lmcorbalan Sep 4, 2026
7e66900
Merge pull request #161 from BootNodeDev/feat/152-bootstrap-instrument
lmcorbalan Sep 4, 2026
e339fda
feat(scripts): deploy the vendored DARs and run the token registry on…
lmcorbalan Sep 4, 2026
186f9e8
fix(scripts): apply the vesting-DAR wording and match the registry's …
lmcorbalan Sep 4, 2026
a90c207
fix(scripts): restart a stale registry, pass bootstrap the participan…
lmcorbalan Sep 4, 2026
8fdfe30
Merge pull request #162 from BootNodeDev/feat/153-dev-stack-registry
lmcorbalan Sep 4, 2026
321a183
fix(scripts): repair the main-module guards and scope the registry re…
lmcorbalan Sep 4, 2026
2ec76fc
Merge pull request #163 from BootNodeDev/feat/154-token-forge-side
lmcorbalan Sep 4, 2026
7bc00c6
feat(frontend): learn the instrument from the registry and move to th…
lmcorbalan Sep 7, 2026
4f55407
fix(frontend): name the registry when it is unreachable and correct t…
lmcorbalan Sep 7, 2026
e58639d
Merge pull request #172 from BootNodeDev/feat/166-registry-client
lmcorbalan Sep 7, 2026
69db6b2
feat(frontend): move the vesting choices onto the instrument config
lmcorbalan Sep 7, 2026
f777054
fix(frontend): repoint the vesting readers at the renamed fields and …
lmcorbalan Sep 7, 2026
1a3a2da
fix(frontend): let a registry failure be retried and normalize its ba…
lmcorbalan Sep 7, 2026
aeb67c8
Merge pull request #174 from BootNodeDev/feat/167-config-commands
lmcorbalan Sep 7, 2026
affadd9
build: bump canton-token-forge to v0.3.0 for the registry CORS support
lmcorbalan Sep 7, 2026
8339b12
feat(frontend): select token holdings instead of splitting, and add t…
lmcorbalan Sep 7, 2026
a57eefa
fix(frontend): refuse a non-positive grant total and repoint the arch…
lmcorbalan Sep 7, 2026
cbfc553
docs(frontend): record what Accept discloses to the receiver under wh…
lmcorbalan Sep 7, 2026
a176513
Merge pull request #176 from BootNodeDev/feat/168-holding-selection
lmcorbalan Sep 7, 2026
164c018
feat(frontend): forward the registry routes from the deployed origin
lmcorbalan Sep 7, 2026
862bd37
fix(frontend): test the registry forwarder and fix its url and error …
lmcorbalan Sep 7, 2026
be5dcff
Merge pull request #177 from BootNodeDev/feat/169-registry-proxy
lmcorbalan Sep 7, 2026
4da0fea
feat(frontend): show the DBT instrument and add the faucet action
lmcorbalan Sep 7, 2026
d5a4f10
fix(frontend): bind the config cid to its disclosure, name the amount…
lmcorbalan Sep 8, 2026
46810dc
Merge pull request #178 from BootNodeDev/feat/170-instrument-faucet
lmcorbalan Sep 8, 2026
7769830
fix(frontend): validate the trimmed registry base and dedupe the disc…
lmcorbalan Sep 8, 2026
f79f985
Merge pull request #183 from BootNodeDev/feat/155-registry-backend
lmcorbalan Sep 8, 2026
9e29d49
build(scripts): deploy only the vendored dars and drop the dpm requir…
lmcorbalan Sep 8, 2026
c8da332
Merge pull request #187 from BootNodeDev/feat/185-dev-stack-no-dpm
lmcorbalan Sep 8, 2026
b0af43e
build: delete the amulet-vesting package and its build
lmcorbalan Sep 8, 2026
e4f5649
Merge pull request #189 from BootNodeDev/feat/184-delete-amulet-package
lmcorbalan Sep 8, 2026
af2cea9
docs: drop the canton coin path from the repository docs
lmcorbalan Sep 8, 2026
9aad464
Merge pull request #190 from BootNodeDev/feat/186-docs-sweep
lmcorbalan Sep 8, 2026
668df58
Merge pull request #191 from BootNodeDev/feat/156-delete-amulet-path
lmcorbalan Sep 8, 2026
61e2c57
feat(dapp): reserve only the granted amount when a grant is created
lmcorbalan Sep 8, 2026
69af401
fix(dapp): repair a missing funding blob on the funder's next view
lmcorbalan Sep 8, 2026
e69083f
fix(dapp): bound the reconcile read and keep one blob per reserved ho…
lmcorbalan Sep 8, 2026
4aeafa4
fix(dapp): count a blobless holding as a miss and spend no budget on …
lmcorbalan Sep 9, 2026
5f02efb
Merge pull request #192 from BootNodeDev/feat/180-reserve-granted-amount
lmcorbalan Sep 9, 2026
0c98f28
feat(dapp): build the proposal cancel and reject commands
lmcorbalan Sep 9, 2026
2daafe0
feat(dapp): end an unaccepted grant from either side and forget what …
lmcorbalan Sep 9, 2026
3f67b16
feat(dapp): wire the proposal cancel and decline through the store
lmcorbalan Sep 9, 2026
f06c2e0
feat(dapp): offer the funder a cancel and the receiver a decline on a…
lmcorbalan Sep 9, 2026
b5dc7c7
docs(dapp): say that an unaccepted grant can now be ended from either…
lmcorbalan Sep 9, 2026
d104519
fix(dapp): pin the ending grant's role and give each pending-grant bu…
lmcorbalan Sep 9, 2026
75fec7e
fix(dapp): survive a browser that cannot record an ended grant, and p…
lmcorbalan Sep 9, 2026
169b360
fix(dapp): stamp the synchronizer on the submission itself and keep a…
lmcorbalan Sep 9, 2026
c47f0b2
fix(dapp): re-arm the close guard under StrictMode and stop a pending…
lmcorbalan Sep 9, 2026
eaa6b0a
Merge pull request #197 from BootNodeDev/feat/181-end-pending-grant
lmcorbalan Sep 9, 2026
fee830e
Merge branch 'main' into feat/157-token-forge-migration
lmcorbalan Sep 9, 2026
7577d0a
Merge pull request #200 from BootNodeDev/chore/199-merge-main
lmcorbalan Sep 9, 2026
6048f4a
fix: stop bootstrap orphaning holdings on restart and close the rest …
lmcorbalan Sep 10, 2026
6f1b1b8
fix(dapp): require the deployed env vars on Vercel alone, not in ever…
lmcorbalan Sep 10, 2026
7518821
fix: match the dApp's operator lookup to bootstrap's stable hint and …
lmcorbalan Sep 10, 2026
f9e9eff
Merge branch 'main' into feat/157-token-forge-migration
lmcorbalan Sep 10, 2026
3cc4beb
Merge branch 'main' into feat/157-token-forge-migration
gabitoesmiapodo Sep 11, 2026
b5eb0a6
docs: keep postcss in knip's ignoreDependencies
gabitoesmiapodo Sep 11, 2026
dd75b09
Merge remote-tracking branch 'origin/main' into feat/157-token-forge-…
gabitoesmiapodo Sep 11, 2026
b8a3b12
Merge remote-tracking branch 'origin/main' into feat/157-token-forge-…
gabitoesmiapodo Sep 16, 2026
e560325
Merge remote-tracking branch 'origin/main' into feat/157-token-forge-…
lmcorbalan Oct 7, 2026
7147e3d
docs: drop the wallet-kernel party rule, which only guarded Amulet gr…
lmcorbalan Oct 8, 2026
d3e7eb1
Merge remote-tracking branch 'origin/main' into feat/157-token-forge-…
lmcorbalan Oct 8, 2026
9f9647e
Merge remote-tracking branch 'origin/main' into feat/157-token-forge-…
gabitoesmiapodo Oct 23, 2026
0e5d99b
feat(canton-create): offer the token vesting example
gabitoesmiapodo Oct 23, 2026
9c422c4
build: run the token vesting stack from the root
gabitoesmiapodo Oct 23, 2026
6387c3c
docs: describe the token vesting example and the stack hooks
gabitoesmiapodo Oct 23, 2026
d57a187
fix(canton-create): keep each app's bootstrap log apart in dev-stack.sh
gabitoesmiapodo Oct 23, 2026
93c7075
fix(token-vesting): store the registry's own pid
gabitoesmiapodo Oct 23, 2026
4f2d4d0
chore: test an example's api and scripts before each commit
gabitoesmiapodo Oct 23, 2026
f265a50
refactor(token-vesting): start the registry from a file bootstrap writes
gabitoesmiapodo Oct 23, 2026
a86f820
build: keep the CANTON_APP_DIR default to mint-token alone
gabitoesmiapodo Oct 23, 2026
919a5bf
chore: run only an example's api and scripts tests for those folders
gabitoesmiapodo Oct 23, 2026
282dfe6
chore: exempt every scripts folder from the import alias rule
gabitoesmiapodo Oct 23, 2026
4c566d0
Merge branch 'main' into feat/token-vesting-example
lmcorbalan Oct 27, 2026
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.dar binary
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ typedoc/
.env
.env.local
.env.fivenorth
.env.registry

# typescript
*.tsbuildinfo
Expand Down
8 changes: 6 additions & 2 deletions .lintstagedrc.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
const examples = (files) => [...new Set(files.map((file) => file.match(/example-dapps\/[^/]+/)?.[0]))]

export default {
'canton-connect/src/**/*.{ts,tsx,js,jsx}': () => 'pnpm -C canton-connect test',
'canton-dappbooster/src/**/*.{ts,tsx,js,jsx}': () => 'pnpm -C canton-dappbooster test',
'example-dapps/*/src/**/*.{ts,tsx,js,jsx}': (files) =>
[...new Set(files.map((file) => file.match(/example-dapps\/[^/]+/)?.[0]))].map(
(example) => `pnpm -C ${example} test`,
examples(files).map((example) => `pnpm -C ${example} test`),
'example-dapps/*/{api,scripts}/**/*.{ts,mjs,sh}': (files) =>
examples(files).map(
(example) => `pnpm -C ${example} exec vitest run --passWithNoTests api scripts`,
),
'canton-create/**/*.{ts,tsx,mjs,sh,json}': () => 'pnpm -C canton-create test',
'{canton-create/scaffold/*/scripts,scripts}/*.{mjs,sh}': () =>
Expand Down
34 changes: 21 additions & 13 deletions CLAUDE.md

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ The dApp runs on http://localhost:3012 and Wallet Gateway on http://localhost:30
Connect through Wallet Gateway with `unsafe` as the client secret. Use `wallet-kernel` as the
signing provider when creating parties.

### Token Vesting dApp

```bash
pnpm dev:token-vesting # interactive menu
pnpm dev:token-vesting up # LocalNet, both DARs, token registry, Wallet Gateway and dApp
pnpm dev:token-vesting down # stop everything and keep the ledger
```

The dApp runs on http://localhost:3012, its token registry on http://localhost:3013 and Wallet
Gateway on http://localhost:3030. Both examples share the LocalNet, so stop one before you start the
other.

## Before opening a pull request

```bash
Expand Down
19 changes: 15 additions & 4 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
| Wallet Gateway (external: [`canton-network/wallet`](https://github.com/canton-network/wallet)) | Node + Express + SQLite | The CIP-0103 wallet the dApp connects to: it holds the session, signs through the participant, and serves its own user UI. A root dev dependency, which `pnpm dev:amulet-vesting` runs on the host from `canton-create/scaffold/localnet/wallet-gateway.config.json` |
| `scripts/` | Node + Bash + JSON | The root tooling: the doc, anatomy and version checks, the release bump, the `typedoc` configs and the gitleaks installer. See [`CLAUDE.md`](CLAUDE.md) |
| `example-dapps/amulet-vesting/` | Vite + React + Ark UI + Tailwind v4 + `zustand` + react-router + Daml | Canton Coin **vesting** dApp, published as an example. Every read and write goes through the connected CIP-0103 wallet via `canton-connect`. Its `daml/` holds the `amulet-vesting` DAR: the vesting factory, proposal, contract and residual-claim templates. They hold real Canton Coin in escrow as a Splice `LockedAmulet`, and come vendored from [`BootNodeDev/cc-vesting-contracts`](https://github.com/BootNodeDev/cc-vesting-contracts). The build fetches its Splice data-dependencies rather than committing them. `scripts/bootstrap-vesting.mjs` creates the operator and its factory |
| `example-dapps/token-vesting/` | Vite + React + Ark UI + Tailwind v4 + `zustand` + react-router | The same **vesting** dApp over a `DBT` instrument from [`BootNodeDev/canton-token-forge`](https://github.com/BootNodeDev/canton-token-forge), published as an example. Its `vesting` and `canton-token-forge` DARs ship as binaries in `vendor/`, so it builds no contract. `scripts/bootstrap-vesting.mjs` uploads them and creates the operator, its factory and the instrument; `scripts/registry.sh` runs the canton-token-forge registry the dApp reads the instrument from |
| `canton-create/` | TypeScript + Ink + React 19 + `tsdown` | The `create-canton-dappbooster` installer. Its `scaffold/daml-tooling/` and `scaffold/localnet/` scripts are also the local loop: the Splice dependency fetch, the DAR build and upload, the token mint and `dev-stack.sh`. See [`canton-create/architecture.md`](canton-create/architecture.md) |
| `canton-connect/` | TypeScript + React 19 | wagmi-style hooks wrapping the `dapp-sdk` facade |
| `canton-dappbooster/` | TypeScript + React 19 + `tsdown` | L2 headless UI components, zero styling, plus the theme runtime and the pure utilities under the components, exact-decimal amounts included |
Expand Down Expand Up @@ -45,6 +46,11 @@ flowchart TD
> connected party is a stakeholder of either. `transferContext.ts` reads both from Scan, at
> `VITE_SCAN_API_URL`, and the browser makes those calls itself.

`example-dapps/token-vesting` takes the same wallet path and has no Scan call. Its one call off the
ledger goes to the canton-token-forge registry on `http://localhost:3013`, for the instrument and the
`InstrumentConfig` each value-moving choice discloses. The registry reads the ledger with
`CANTON_BACKEND_TOKEN`, as the scripts do.

`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 local stack does not start the app-provider
Expand All @@ -66,6 +72,7 @@ State boundaries:
| --- | --- | --- |
| dApp frontend | `http://localhost:3012` | example dApp |
| Wallet Gateway | `http://localhost:3030` | the wallet: user UI, and `/api/v0/dapp` for the dApp |
| Token registry | `http://localhost:3013` | the token example's canton-token-forge registry |
| app-user Wallet UI | `http://wallet.localhost:2000` | optional official Splice wallet UI |
| app-user Ledger API | `grpc://localhost:2901` | SDK/tools |
| app-user administration API | `grpc://localhost:2902` | SDK/tools |
Expand All @@ -90,10 +97,9 @@ State boundaries:
| `CANTON_AUTH_SECRET` | `.env` | unsafe local signing secret used only by the token script |
| `CANTON_BACKEND_TOKEN` | `.env` | generated JWT consumed by the DAR upload and the vesting bootstrap |

`example-dapps/amulet-vesting/.env` is the only one that matters: the signing recipe
`mint-token.mjs` reads, plus the token `deploy-dar.sh` and the example's
`scripts/bootstrap-vesting.mjs` send. The root scripts point the first two at it through
`CANTON_APP_DIR`. Minting is offline, so it needs nothing running, which is what lets
Each example's own `.env` is the one that matters: the signing recipe `mint-token.mjs` reads, plus
the token `deploy-dar.sh` and the example's `scripts/bootstrap-vesting.mjs` send. The root scripts
point the first two at it through `CANTON_APP_DIR`. Minting is offline, so it needs nothing running, which is what lets
`pnpm dev:amulet-vesting up` mint `CANTON_BACKEND_TOKEN` into a fresh `.env` before anything is up. The
LocalNet reads its own `canton-barebones.config.json`, which `dev-stack.sh` scaffolds into
`.canton-localnet/` and git does not track.
Expand All @@ -113,6 +119,7 @@ that secret and nothing else.
| --- | --- |
| `pnpm dev:amulet-vesting up` | the whole local loop: LocalNet, DAR, bootstrap, Wallet Gateway, dApp dev server |
| `pnpm dev:amulet-vesting down` | stop the gateway and the dApp dev server, stop the LocalNet |
| `pnpm dev:token-vesting up` / `down` | the same loop on the token example, which also starts and stops its registry |
| `pnpm run wallet-gateway` | the gateway alone, from `canton-create/scaffold/localnet/wallet-gateway.config.json` |
| `pnpm exec canton-barebones start` / `stop` / `reset` / `status` | the LocalNet itself, run from `.canton-localnet/` |
| `node canton-create/scaffold/localnet/scripts/localnet-config.mjs <dir>` | scaffold that directory and apply the flags nginx needs |
Expand All @@ -122,6 +129,10 @@ that secret and nothing else.
| `pnpm run bootstrap` | create the vesting operator and its factory |
| `pnpm run app:dev` | start the dApp frontend |

`dev-stack.sh` runs an app's own `stack:up` script after `bootstrap` and its `stack:down` script on
`down`, when the app defines them. The token example starts and stops its registry there, from
the `.env.registry` its bootstrap writes.

`dev-stack.sh` shells out to the LocalNet tool in the directory passed as its second argument
(`pnpm dev:amulet-vesting up <dir>`). Otherwise it uses `CANTON_LOCALNET_DIR`, and then
`.canton-localnet/`. It scaffolds that directory on `up` from the pinned tool's own template, and
Expand Down
10 changes: 10 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,16 @@
}
}
}
},
{
"includes": ["**/scripts/**"],
"linter": {
"rules": {
"style": {
"noRestrictedImports": "off"
}
}
}
}
]
}
5 changes: 4 additions & 1 deletion canton-create/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ together, see [`architecture.md`](architecture.md).
sets. Any other overwrite is a design error in `scaffold/`, and `scaffold` throws
`ScaffoldConflictError`. So does any other field in a layer's `package.json`, apart from the
layer's own `name`, `private`, `version` and `type`, because the merge would drop it.
- What an app runs beside the ledger, such as the token example's registry, stays in the app. It
starts and stops through the app's own `stack:up` and `stack:down` scripts, which `dev-stack.sh`
runs when they exist, so the `localnet/` layer never learns about one app's services.
- A layer's `README.md` is a section that gets appended, so it starts with a `##` heading and never
with a title.
- The root range rule has no exception in `scaffold/`: the tarball ships the folders as they are, so
Expand All @@ -30,7 +33,7 @@ together, see [`architecture.md`](architecture.md).
`_pnpm-workspace.yaml`, and `scaffold` renames it back. npm drops a `.gitignore` from every
tarball, and a `pnpm-workspace.yaml` inside the monorepo makes pnpm treat the folder as a
workspace root of its own.
- The monorepo runs the `daml-tooling/` and `localnet/` scripts on `example-dapps/amulet-vesting`
- The monorepo runs the `daml-tooling/` and `localnet/` scripts on the examples in `example-dapps/`
instead of keeping copies. Each script finds the app's `daml/` and `.env` through
`CANTON_APP_DIR`, which defaults to its own project. A new script reads the app from there, never
from a path to its own folder. Their tests sit beside them, and `files` keeps the tests out
Expand Down
4 changes: 4 additions & 0 deletions canton-create/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,22 @@ commit, and prints the next steps. If a step fails or you press Ctrl-C, it remov
Alternatively, you can start with a preselected dApp or local network:

- **Amulet Vesting:** a vesting dApp frontend and its contracts.
- **Token Vesting:** a vesting dApp over a canton-token-forge token, with its registry.
- **Barebones:** a minimal local Canton stack for developer workflows.

With npm:

```shell
npm create canton-dappbooster -- --example amulet-vesting # Amulet Vesting dApp
npm create canton-dappbooster -- --example token-vesting # Token Vesting dApp
npm create canton-dappbooster -- --localnet # Barebones local network
```

With pnpm:

```shell
pnpm create canton-dappbooster --example amulet-vesting # Amulet Vesting dApp
pnpm create canton-dappbooster --example token-vesting # Token Vesting dApp
pnpm create canton-dappbooster --localnet # Barebones local network
```

Expand Down Expand Up @@ -65,6 +68,7 @@ pnpm create canton-dappbooster my-dapp --example amulet-vesting --localnet # A
|---|---|---|---|
| `Starter dApp` | none | ✓ | Barebones dApp that connects to a wallet and gives you a sample contract |
| `Amulet Vesting` | `--example amulet-vesting` | | A vesting dApp frontend and its contracts |
| `Token Vesting` | `--example token-vesting` | | A vesting dApp over a canton-token-forge token, with its registry |

### Local networks

Expand Down
6 changes: 3 additions & 3 deletions canton-create/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ excludes.
|---|---|---|
| `starter/` | the dApp is the starter | the complete starter app and a sample contract in `daml/` |
| `daml-tooling/` | the app has `daml/daml.yaml` | `build-dar`, `deploy-dar` and `fetch-daml-deps` |
| `localnet/` | `--localnet` or Barebones | `dev-stack.sh`, `localnet-config.mjs`, `mint-token.mjs`, `wallet-gateway.config.json`, and `canton-barebones` and the gateway in `devDependencies` |
| `localnet/` | `--localnet` or Barebones | `dev-stack.sh`, `localnet-config.mjs`, `mint-token.mjs`, `wallet-gateway.config.json`, and `canton-barebones` and the gateway in `devDependencies`. `dev-stack.sh` runs the app's own `bootstrap`, `stack:up` and `stack:down` scripts when it has them |

`scaffold` in `src/scaffold.ts` copies the app first, then applies `daml-tooling/` and `localnet/`
on top of it in that order. `src/merge.ts` decides how each layer file lands: merged into the
manifest, appended, merged by key, or a conflict. `finalizeManifest` then strips what only a
published package needs (`version`, `repository`, `files`, `publishConfig` and the rest), names the
manifest after the folder and marks it private.

The monorepo's own loop runs the same `daml-tooling/` and `localnet/` scripts on
`example-dapps/amulet-vesting`, from the root `package.json`.
The monorepo's own loop runs the same `daml-tooling/` and `localnet/` scripts on the examples in
`example-dapps/`, from the root `package.json`.

Each folder is a workspace package, so the monorepo checks their types against the libraries'
source. Their ranges resolve as the Packaging section of the root
Expand Down
2 changes: 1 addition & 1 deletion canton-create/demo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion canton-create/scaffold/localnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Then pick Wallet Gateway in the dApp's picker and log in with the client secret
party in the gateway with `wallet-kernel` as the signing provider.

On its first run, `pnpm stack up` mints `CANTON_BACKEND_TOKEN` into `.env`. When the project has a
contract, it then builds and deploys it, and runs the `bootstrap` script if there is one. To
contract, it then builds and deploys it. It runs the `bootstrap` script if there is one, then the
`stack:up` script, which starts anything else the app needs. `pnpm stack down` runs `stack:down`. To
wipe the ledger, run `canton-barebones reset` inside `.canton-localnet/`. You then have to create
the gateway's parties again.

Expand Down
14 changes: 11 additions & 3 deletions canton-create/scaffold/localnet/scripts/dev-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#
# Every step is a script in package.json, which is the contract this file relies on:
# `mint-token`, `wallet-gateway`, `dev`, `build-dar` and `deploy-dar` when the app has a contract
# (daml/daml.yaml), and optionally `bootstrap`. `dev` and `bootstrap` are the app's own; the rest
# run at the pnpm workspace root holding the app, which in a project is the same package.json.
# (daml/daml.yaml), and optionally `bootstrap`, `stack:up` and `stack:down`. `dev`, `bootstrap`
# and the two hooks are the app's own; the rest run at the pnpm workspace root holding the app,
# which in a project is the same package.json. The hooks start and stop whatever else the app
# runs beside the ledger, and get DEV_STACK_RUN_DIR for its logs.
#
# The LocalNet belongs to @bootnodedev/canton-barebones, pinned in
# package.json and driven from the directory holding its config. `up` scaffolds that
Expand Down Expand Up @@ -117,7 +119,7 @@ die() {
}

# Keep in step with the `step` calls in up(), which are the list.
STEP_TOTAL=$((8 + 2 * HAS_CONTRACT))
STEP_TOTAL=$((9 + 2 * HAS_CONTRACT))
STEP_INDEX=0
STEP_NAME=stack
STEP_START=$SECONDS
Expand Down Expand Up @@ -402,6 +404,10 @@ Fix what build-dar printed above, then run 'pnpm stack up' again (menu: Stack up
step bootstrap "Running the bootstrap script, if the project defines one..."
pnpm -C "$APP_DIR" run --if-present bootstrap || die "The bootstrap script failed."

step services "Starting the app's own services, if the project defines any..."
DEV_STACK_RUN_DIR="$RUN_DIR" pnpm -C "$APP_DIR" run --if-present stack:up 3>&- 4>&- \
|| die "The stack:up script failed."

step wallet-gateway "Starting the Wallet Gateway -> http://localhost:3030"
start_wallet_gateway

Expand Down Expand Up @@ -469,6 +475,8 @@ stop_port() { # stop_port <port> <label>
down() {
# 1. Background processes
stop_pidfile "$DAPP_PID" "dApp dev server"
DEV_STACK_RUN_DIR="$RUN_DIR" pnpm -C "$APP_DIR" run --if-present stack:down \
|| warn "The stack:down script reported an error"
stop_port 3012 "dApp dev server"
stop_pidfile "$GW_PID" "Wallet Gateway"
stop_port 3030 "Wallet Gateway"
Expand Down
4 changes: 4 additions & 0 deletions canton-create/src/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export const EXAMPLES: Record<string, Omit<RadioOption, 'value'>> = {
label: 'Amulet Vesting',
hint: 'A vesting dApp frontend and its contracts.',
},
'token-vesting': {
label: 'Token Vesting',
hint: 'A vesting dApp over a canton-token-forge token, with its registry.',
},
}

/** Every dApp the list offers: the starter first, then the examples. */
Expand Down
12 changes: 10 additions & 2 deletions canton-dappbooster/src/utils/readInstruments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,15 @@ interface Page {
nextPageToken: string | undefined
}

// A registry that accepts the socket and never answers would otherwise leave the caller's own
// `await` pending for good, with nothing to render and no error to show for it.
const REQUEST_TIMEOUT_MS = 15_000

const get = async (url: string): Promise<unknown> => {
const response = await fetch(url, { headers: { accept: 'application/json' } })
const response = await fetch(url, {
headers: { accept: 'application/json' },
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
})
if (!response.ok) {
throw new Error(`${url} answered ${response.status}`)
}
Expand Down Expand Up @@ -63,7 +70,8 @@ const toPage = (value: unknown): Page => {
* Reads a registry's instrument metadata, following its pages up to a limit of 100, so the answer
* is the registry's whole catalogue and a registry that will not stop paging cannot hang the caller.
*
* @throws where either request answers anything but 200, or the reply is not JSON.
* @throws where either request answers anything but 200, does not answer within 15s, or replies
* with something that is not JSON.
*
* @example
* const instruments = await readInstruments('https://registry.example/api')
Expand Down
47 changes: 47 additions & 0 deletions example-dapps/token-vesting/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
###############################################################################
# Participant
###############################################################################
CANTON_JSON_API_URL=http://localhost:2975

###############################################################################
# Scripts / token generation
###############################################################################
CANTON_AUTH_AUDIENCE=https://canton.network.global
# Never copy CANTON_AUTH_SECRET into a browser.
CANTON_AUTH_SECRET=unsafe
# On a LocalNet the stack fills this in; or run `pnpm mint-token`
CANTON_BACKEND_TOKEN=

# The token registry's own options, which scripts/registry.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_REGISTRY_URL=http://localhost:3013
VITE_WALLET_GATEWAY_URL=http://localhost:3030/api/v0/dapp

###############################################################################
# Wallet Connect
###############################################################################
VITE_NETWORK_ID=canton:local
VITE_WALLET_CONNECT_PROJECT_ID=

###############################################################################
# Vercel
###############################################################################
# All three are set on the deployed project rather than in this file, and none can be localhost
# there: the bundle asks its own origin for the registry, only the function names the registry,
# and the gateway has to be one on the same network as that registry.
#VITE_REGISTRY_URL=/api/registry
#REGISTRY_URL=https://registry.example
#VITE_WALLET_GATEWAY_URL=https://gateway.example/api/v0/dapp
# Set by the build command in vercel.json, and by any other host that serves this
# bundle: it is what makes the three URLs 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
3 changes: 3 additions & 0 deletions example-dapps/token-vesting/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Agent Configuration
This project's canonical agent configuration lives in [`CLAUDE.md`](./CLAUDE.md).
Agents read `AGENTS.md` natively; load `CLAUDE.md` as the single source of truth.
Loading
Loading