Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
9e6ba7d
fix(connect): act as the party the kit shows, or refuse
fernandomg Sep 1, 2026
6a6c699
feat: split the wallet button into two faces and a chooser
gabitoesmiapodo Sep 1, 2026
211f517
feat: style the two wallet buttons and drop the popover rules
gabitoesmiapodo Sep 1, 2026
cc71887
feat: give the header an account dropdown
gabitoesmiapodo Sep 1, 2026
3197d9f
feat: replace the footer strip with the landing page's
gabitoesmiapodo Sep 1, 2026
4b8ec89
docs: the session chain runs through the two faces and the account menu
gabitoesmiapodo Sep 1, 2026
81b1f41
chore: adopt SDLC starter kit v2026.09
gabitoesmiapodo Sep 2, 2026
190c7bc
Merge branch 'main' into fix/73-execute-acts-as-displayed-party
fernandomg Sep 2, 2026
13792a2
style: match the footer's bottom padding to the header's top
gabitoesmiapodo Sep 2, 2026
cb68e67
docs: point the reference links at docs.dappbooster.cc
gabitoesmiapodo Sep 2, 2026
8a1f2ce
fix: offer connect instead of a dead unlock button
gabitoesmiapodo Sep 2, 2026
b5583eb
docs: state when a ternary and when a guard clause
gabitoesmiapodo Sep 2, 2026
5755d6e
Merge branch 'main' into feat/135-account-button
gabitoesmiapodo Sep 2, 2026
80268a4
refactor: hand the account menu the party it renders
gabitoesmiapodo Sep 3, 2026
28ad9bf
fix(connect): key call on the party id, not the object
fernandomg Sep 3, 2026
af46845
docs(connect): correct the party and refusal claims
fernandomg Sep 3, 2026
ebd295e
Merge pull request #137 from BootNodeDev/fix/73-execute-acts-as-displ…
fernandomg Sep 3, 2026
02007d4
Merge branch 'main' into feat/135-account-button
fernandomg Sep 3, 2026
2e0eb30
test: keep jsdom's localStorage under Node 26
fernandomg Sep 3, 2026
1ebe9bc
ci: run the test job on Node 26 too
fernandomg Sep 3, 2026
5f202dd
chore: update localnet and wallet-service to their latest releases
gabitoesmiapodo Sep 3, 2026
66c41a7
Merge pull request #143 from BootNodeDev/chore/update-localnet-and-wa…
gabitoesmiapodo Sep 3, 2026
da50696
Merge remote-tracking branch 'origin/main' into feat/134-faucet
gabitoesmiapodo Sep 3, 2026
30fe06b
Merge branch 'main' into fix/132-node26-localstorage
fernandomg Sep 3, 2026
d186296
Merge branch 'main' into feat/135-account-button
gabitoesmiapodo Sep 3, 2026
d2d9a7f
feat(connect): a connect attempt takes a cancel
fernandomg Sep 3, 2026
6224956
feat(connect): a cancelled connect closes the picker window
fernandomg Sep 3, 2026
7395036
refactor(connect): one word for a connect nobody is waiting on
fernandomg Sep 3, 2026
a94e0bc
feat(connect): expose the cancel as useConnect().cancelConnect
fernandomg Sep 3, 2026
86ed04b
feat(dappbooster): the connecting button cancels the attempt
fernandomg Sep 3, 2026
2fea4f0
feat(frontend): hint that the connecting button cancels
fernandomg Sep 3, 2026
e33b534
refactor: drop the unreachable null return from the account menu
gabitoesmiapodo Sep 3, 2026
a9fc774
Merge pull request #136 from BootNodeDev/feat/135-account-button
gabitoesmiapodo Sep 3, 2026
0b64770
Merge branch 'main' into fix/132-node26-localstorage
fernandomg Sep 3, 2026
68db2c9
Merge branch 'main' into feat/105-cancel-connect
fernandomg Sep 3, 2026
c643f48
Merge pull request #144 from BootNodeDev/fix/132-node26-localstorage
fernandomg Sep 3, 2026
f9e8259
Merge branch 'main' into feat/105-cancel-connect
fernandomg Sep 3, 2026
48860ed
refactor(frontend): rename the token to Amulet and AMT
gabitoesmiapodo Sep 3, 2026
c5aa349
feat(frontend): tap Amulet from the vesting backend
gabitoesmiapodo Sep 3, 2026
3604b68
feat(frontend): rework the account dropdown
gabitoesmiapodo Sep 3, 2026
78c747a
feat(frontend): add a Tap Amulet item to the account menu
gabitoesmiapodo Sep 3, 2026
2d840c6
docs(frontend): describe the Amulet faucet
gabitoesmiapodo Sep 3, 2026
71f8ba8
refactor(frontend): share one copy button
gabitoesmiapodo Sep 3, 2026
3339f16
fix(frontend): flag a grant amount above the balance
gabitoesmiapodo Sep 3, 2026
dd5cffd
refactor(frontend): shorten the grant toast and drop disclosedBytes
gabitoesmiapodo Sep 3, 2026
eba73b2
ci: run the frontend tests in the pre-commit hook
gabitoesmiapodo Sep 3, 2026
62510e3
Merge remote-tracking branch 'origin/main' into feat/134-faucet
gabitoesmiapodo Sep 3, 2026
2b08b3b
fix(frontend): reject a milestone percentage that is not a number
gabitoesmiapodo Sep 3, 2026
7043551
feat(frontend): replace the hand-rolled widgets with Ark UI and lucid…
gabitoesmiapodo Sep 3, 2026
e5831fd
style(frontend): drop the comments from the files this branch touches
gabitoesmiapodo Sep 4, 2026
0deaf75
fix(frontend): repair the create dialog, the icon hover and the scrol…
gabitoesmiapodo Sep 4, 2026
e34002b
refactor(connect): rename the hook flags and error to isPending and e…
fernandomg Sep 4, 2026
99479ff
docs(connect): drop the wagmi counterpart lines from the hooks
fernandomg Sep 4, 2026
6975cec
feat(connect): split disconnect out of useConnect into useDisconnect
fernandomg Sep 4, 2026
7e9ae0c
docs(connect): map the hooks to their wagmi counterparts in one page
fernandomg Sep 4, 2026
07275c8
docs: name the wagmi page as the one hand-written page in the reference
fernandomg Sep 4, 2026
8a6a0d4
Merge pull request #145 from BootNodeDev/feat/134-faucet
gabitoesmiapodo Sep 4, 2026
0f75716
Merge branch 'main' into feat/138-ark-ui
gabitoesmiapodo Sep 4, 2026
bb357c6
Merge branch 'main' into refactor/52-is-pending
fernandomg Sep 4, 2026
3fe53ab
Merge branch 'main' into feat/105-cancel-connect
gabitoesmiapodo Sep 4, 2026
69b1e6b
Merge pull request #147 from BootNodeDev/feat/138-ark-ui
gabitoesmiapodo Sep 4, 2026
44d35ac
Merge branch 'main' into feat/105-cancel-connect
fernandomg Sep 4, 2026
f497256
Merge branch 'main' into refactor/52-is-pending
fernandomg Sep 4, 2026
aabd700
fix(canton-connect): cancelled connect opens no popup
fernandomg Sep 4, 2026
9c1b139
fix(canton-connect): cancel during the party read disconnects
fernandomg Sep 4, 2026
4cdac97
feat(kit): identify a token by its instrument id and show what a part…
gabitoesmiapodo Sep 4, 2026
f277290
refactor(kit): move the token list's roving focus into its own hook
gabitoesmiapodo Sep 4, 2026
05f47a7
style(kit): drop the caret comment
gabitoesmiapodo Sep 4, 2026
38b807f
feat(frontend): offer the token picker in the create form
gabitoesmiapodo Sep 4, 2026
b9eb2b9
feat(kit): read what a party holds off the ledger
gabitoesmiapodo Sep 4, 2026
81d160e
feat(frontend): build the token list from what the party holds
gabitoesmiapodo Sep 4, 2026
4be92c4
feat(kit): read a token registry's instrument metadata
gabitoesmiapodo Sep 4, 2026
f6e0366
feat(frontend): label the token list from the registry
gabitoesmiapodo Sep 4, 2026
9cf301c
feat(frontend): read the curated token list as a third source
gabitoesmiapodo Sep 4, 2026
65436cd
feat(frontend): serve the token list with a LocalNet section
gabitoesmiapodo Sep 4, 2026
31e8009
feat(frontend): ask each token's own registry for its metadata
gabitoesmiapodo Sep 4, 2026
a96af98
feat(kit): merge token sources into one catalogue
gabitoesmiapodo Sep 4, 2026
08e3493
feat(frontend): offer every token the sources know, not only what is …
gabitoesmiapodo Sep 4, 2026
ab3254e
feat(frontend): show the figures this app can act on
gabitoesmiapodo Sep 4, 2026
560f165
fix(frontend): say so when a balance cannot be read
gabitoesmiapodo Sep 4, 2026
668ac32
fix(frontend): keep the picked token's figures up to date
gabitoesmiapodo Sep 4, 2026
7bdd857
fix(frontend): drop a balance that is not this party's, or not readable
gabitoesmiapodo Sep 4, 2026
f15bc76
fix(frontend): re-read the balance when the create dialog opens
gabitoesmiapodo Sep 4, 2026
53ee722
fix(kit): bound how far readInstruments will follow a registry
gabitoesmiapodo Sep 4, 2026
a43298f
fix(frontend): keep the dev server up when a registry key is blank
gabitoesmiapodo Sep 4, 2026
1d85420
docs(frontend): correct what architecture.md says about the amount ce…
gabitoesmiapodo Sep 4, 2026
471604d
refactor(kit): read a JSON reply through one pair of helpers
gabitoesmiapodo Sep 4, 2026
2846dbf
style(kit): bail out with a guard clause, not a ternary
gabitoesmiapodo Sep 4, 2026
7e9214b
style(kit): sort the members of useHoldings' two types
gabitoesmiapodo Sep 4, 2026
ace5fc2
perf(kit): parse each balance once when sorting the token list
gabitoesmiapodo Sep 4, 2026
e3b2634
docs(kit): say where the name and symbol fallback happens
gabitoesmiapodo Sep 4, 2026
c3e817b
refactor(frontend): sum the holdings once per rebuild
gabitoesmiapodo Sep 4, 2026
5bdd0ff
refactor(frontend): give the Canton Coin row one spelling
gabitoesmiapodo Sep 4, 2026
dd97e7e
refactor(frontend): narrow a parsed JSON reply once
gabitoesmiapodo Sep 4, 2026
0e99863
Merge pull request #142 from BootNodeDev/feat/105-cancel-connect
fernandomg Sep 7, 2026
77a73e9
Merge branch 'main' into refactor/52-is-pending
fernandomg Sep 7, 2026
99fa700
Merge pull request #160 from BootNodeDev/refactor/52-is-pending
fernandomg Sep 7, 2026
3d1f033
feat(connect): pass namespace and signingProviderId through to Party
fernandomg Sep 7, 2026
70078b0
feat(connect): add usePartyType
fernandomg Sep 7, 2026
01335dd
Merge branch 'main' into feat/133
gabitoesmiapodo Sep 7, 2026
6674580
refactor(frontend): extract the create grant schedule form
gabitoesmiapodo Sep 7, 2026
44e7dae
refactor(frontend): give each create grant step its own file
gabitoesmiapodo Sep 7, 2026
dd7c10a
refactor(frontend): build the create grant dialog from its step files
gabitoesmiapodo Sep 7, 2026
5e453a8
docs(frontend): point the field links at the create grant details step
gabitoesmiapodo Sep 7, 2026
decbf78
Merge pull request #165 from BootNodeDev/feat/5-party-type
fernandomg Sep 8, 2026
e04c514
Merge pull request #175 from BootNodeDev/feat/139
gabitoesmiapodo Sep 8, 2026
86233e1
Merge branch 'main' into feat/133
gabitoesmiapodo Sep 8, 2026
49733f0
refactor(frontend): drop the AssetListNetwork alias
gabitoesmiapodo Sep 8, 2026
7be02ca
fix(dappbooster): give the useHoldings fixture the new Party fields
gabitoesmiapodo Sep 8, 2026
c7ea34e
feat(wrong network): show topbar strip when the wrong network is sele…
gabitoesmiapodo Sep 8, 2026
a98fd7e
docs(frontend): say what a CNS lookup would and would not answer
gabitoesmiapodo Sep 8, 2026
2876979
fix(dappbooster): report no figure for an amount formatFigure cannot …
gabitoesmiapodo Sep 8, 2026
61b4cae
feat(dappbooster): let a consumer set formatFigure's places and locale
gabitoesmiapodo Sep 8, 2026
271c24f
refactor(frontend): read two-decimal figures through the kit's format…
gabitoesmiapodo Sep 8, 2026
64c6a1d
fix(frontend): say N/A rather than throwing in formatFigureFull
gabitoesmiapodo Sep 8, 2026
40c61bb
fix(frontend): only read token metadata from registries the browser c…
gabitoesmiapodo Sep 8, 2026
26455ff
Merge pull request #171 from BootNodeDev/feat/133
gabitoesmiapodo Sep 8, 2026
2238d9a
fix(wrong network): re-read the networks so the strip tracks a wallet…
gabitoesmiapodo Sep 8, 2026
d55ab3c
fix(wrong network): name only the network the wallet reports
gabitoesmiapodo Sep 8, 2026
0cb837f
fix(wrong network): correct the read, the polling and the message
gabitoesmiapodo Sep 8, 2026
271ae9d
fix(wrong network): read the app's network without waiting for a round
gabitoesmiapodo Sep 8, 2026
cc46fd9
Merge branch 'main' into feat/146
gabitoesmiapodo Sep 8, 2026
f68f3aa
feat(dappbooster): split the cancel out of the connect button
gabitoesmiapodo Sep 8, 2026
64799ee
feat(frontend): swap in the cancel button while a connect runs
gabitoesmiapodo Sep 8, 2026
4a0a03f
docs(connect): point the picker walkthrough at the cancel button
gabitoesmiapodo Sep 8, 2026
eeb4468
feat(dappbooster): give WalletButton a cancel face
gabitoesmiapodo Sep 8, 2026
99dfb87
refactor(frontend): drop the wrapper span around the connect face
gabitoesmiapodo Sep 8, 2026
986fdc6
style: cut the comments that restate the code
gabitoesmiapodo Sep 8, 2026
e02b3a4
refactor(dappbooster): inline the inert click into the two buttons
gabitoesmiapodo Sep 8, 2026
d4bbd3e
Merge pull request #194 from BootNodeDev/feat/164
gabitoesmiapodo Sep 9, 2026
b61cb67
Merge branch 'main' into feat/146
gabitoesmiapodo Sep 9, 2026
f537470
Merge pull request #193 from BootNodeDev/feat/146
gabitoesmiapodo Sep 9, 2026
fee830e
Merge branch 'main' into feat/157-token-forge-migration
lmcorbalan Sep 9, 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
23 changes: 23 additions & 0 deletions .claude/skills/create-issue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ The word limit above is a hard cap, not a guideline. A body that exceeds it is n
- Over the hard cap means delete content, never compress it into one long sentence.
- Steps and criteria are for a human to execute: numbered, imperative, one ultra brief line each, no assumed context.

## Writing Style

Avoid dense technical writing. Use clear, concise, and non-redundant prose. Examples: Simple English Wikipedia, Mr. Rogers, Ernest Hemingway.

Banned vocabulary: do not invent abbreviations, compound labels, framework names, or domain terms.

List of usual words to avoid:

- load-bearing
- blast radius
- footgun
- yak shaving
- belt-and-suspenders
- smoking gun
- spine
- seams
- gate
- substrate

If writing documentation pay special attention to use words a maintainer would search for in the codebase.

If something can be made a list, make it a list. It's easier to read. Prefer avoiding walls of text.

## Step 1: Classify

| Signal | Type |
Expand Down
23 changes: 23 additions & 0 deletions .claude/skills/create-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@ The word limits above are hard caps, not guidelines. A body that exceeds one is
- No preamble. `This PR adds...` → `Adds...`, then delete that too if the bullet below says it.
- Over a hard cap means delete content, never compress it into one long sentence.

## Writing Style

Avoid dense technical writing. Use clear, concise, and non-redundant prose. Examples: Simple English Wikipedia, Mr. Rogers, Ernest Hemingway.

Banned vocabulary: do not invent abbreviations, compound labels, framework names, or domain terms.

List of usual words to avoid:

- load-bearing
- blast radius
- footgun
- yak shaving
- belt-and-suspenders
- smoking gun
- spine
- seams
- gate
- substrate

If writing documentation pay special attention to use words a maintainer would search for in the codebase.

If something can be made a list, make it a list. It's easier to read. Prefer avoiding walls of text.

## Step 0: Scope Check

Run this first. A developer must never discover at the end of the wizard that the branch should have been split.
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm test

test-node-26:
name: 3 - test (node 26)
runs-on: ubuntu-latest
if: github.event.action != 'edited'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 26
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test

commitlint:
name: 4 - commitlint
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion .lintstagedrc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
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',
'dapp/frontend/src/**/*.{ts,tsx,js,jsx}': () => 'pnpm -C dapp/frontend test',
// One task for the whole doc gate, under the name CI uses: typedoc resolves both packages in a
// single run anyway, so splitting it per package bought a second and two ways to drift.
// Snippets live in the READMEs as well as in the doc blocks, so a doc edit re-runs the compile.
'{canton-connect,canton-dappbooster}/{src/**/*.{ts,tsx},doc-fixtures.d.ts,README.md,architecture.md,typedoc.json}':
'{canton-connect,canton-dappbooster}/{src/**/*.{ts,tsx},doc-fixtures.d.ts,README.md,architecture.md,coming-from-wagmi.md,typedoc.json}':
() => 'pnpm docs:check',
// Either side of the L2/L3 contract moving on its own is what this catches.
'{canton-dappbooster/src/{components,providers}/**/anatomy.ts,canton-theme/src/**/*.css}': () =>
Expand Down
9 changes: 6 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- starter-kit: v2026.08 -->
<!-- starter-kit: v2026.09 -->

# Agent Configuration — Canton dAppBooster

Expand Down Expand Up @@ -30,7 +30,7 @@ Use one reader per doc type, layered by scope:
| `AGENTS.md` | Agent compatibility loader | Three-line shim beside every `CLAUDE.md`, pointing to the sibling `CLAUDE.md`. It is never canonical. |
| `architecture.md` | Human or agent: what are the structural seams and internal subsystems? | Root always for cross-component seams. Subproject only when internals outgrow the README: three or more interacting subsystems, non-trivial control flow, or named abstractions. |
| `architecture/<topic>.md` | Human or agent editing one subsystem: how does it behave in full? | Only beside an `architecture.md` that indexes it, when a section outgrows the seam it describes. The index keeps the seam and links the chapter; no chapter without its index entry. |
| Generated reference | Human: what does this export do and how do I call it? | Not hand-maintained and not a file anyone edits. `typedoc.json` builds it from the JSDoc on `canton-dappbooster` and `canton-connect`'s barrels; `@internal` keeps a symbol off it. Fix the doc block, never the site. |
| Generated reference | Human: what does this export do and how do I call it? | Not hand-maintained and not a file anyone edits. `typedoc.json` builds it from the JSDoc on `canton-dappbooster` and `canton-connect`'s barrels; `@internal` keeps a symbol off it. Fix the doc block, never the site. `canton-connect/coming-from-wagmi.md`, published through `projectDocuments`, is the one hand-written page in it: an exception for a mapping to another library's API, not a pattern to repeat. |

Current distribution:

Expand Down Expand Up @@ -83,7 +83,7 @@ A README may state that a contract exists and link to it. It may not restate it.

| Path | Purpose | Stack | Port |
|------|---------|-------|------|
| [`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 + 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) |
Expand Down Expand Up @@ -330,6 +330,9 @@ package, because only `canton-dappbooster` splits markup from styles across a pa
`onClick ?? doTheThing` silently drops the behaviour the component exists for. Where a state
machine owns the handler, merge through its own utility (`mergeProps` in Zag) rather than by
hand, so a handler the library adds later is not missed.
- **A ternary is for a two-way toggle between two things to render; a guard clause is for bailing
out of the whole render** (loading, error, no data). Two real faces get
`cond ? <A /> : <B />`; a bail-out gets an `if` above the return.
- Tests assert on roles, accessible names, and whatever contract the component declares. Never on
styling.

Expand Down
2 changes: 1 addition & 1 deletion architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| `scripts/` | Bash + Node | The local loop: `dev-stack.sh`, the DAR upload, the token mint, the vesting bootstrap |
| wallet-service (external: [BootNodeDev/canton-wallet-service](https://github.com/BootNodeDev/canton-wallet-service)) | Node 24 + Express 5 + TypeScript + `@canton-network/wallet-sdk` | Bridge the wallet uses for external-party onboarding and participant JSON API calls. A git dependency pinned to a tag, run on the host by `scripts/dev-stack.sh` |
| token registry (external: [BootNodeDev/canton-token-forge](https://github.com/BootNodeDev/canton-token-forge)) | Node + Express + TypeScript | Read-only CIP-56 registry over the `canton-token-forge` package: serves instrument metadata and the transfer-factory choice context. A git dependency pinned to a tag, run on the host by `scripts/dev-stack.sh` |
| `dapp/frontend/` | Vite + React + Tailwind v4 + zustand + react-router | `DBT` **vesting** dApp; every read and write goes through the connected CIP-0103 wallet via `canton-connect`, and the `InstrumentConfig` every write carries comes from the token registry |
| `dapp/frontend/` | Vite + React + Ark UI + lucide-react + Tailwind v4 + zustand + react-router | `DBT` **vesting** dApp; every read and write goes through the connected CIP-0103 wallet via `canton-connect`, and the `InstrumentConfig` every write carries comes from the token registry |
| `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 |
| `canton-theme/` | CSS | L3 plain-CSS theme: `--cnc-*` tokens + prestyled defaults |
Expand Down
26 changes: 17 additions & 9 deletions canton-connect/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,20 @@ the session and the transports. Browser-only, and built to stay cheap to delete.
lifecycle; remount the provider (`key`) to change it.
- **A state carries a tag for what it means to the outside.** The tags union in
`machine/connectionMachine.ts` is the authority, and no other module names a state. A state that
answers an operation must carry its tag or the bridge waits forever: `waitFor` is unbounded here
(#105).
answers an operation must carry its tag or the bridge waits forever: `waitFor` is unbounded here,
and no clock will rescue it.
- **A state's `exit` clears what that state alone justified.** `party` is cleared on leaving
`session.authenticated`, because a wallet that stops serving requests has none to offer, and a
lock cannot be told from a wallet-side disconnect. `sdk` has no exit; nothing outlives it.
- **Listeners register only inside a state's `invoke`.** `sdk.onX` binds to the current client and
`sdk.connect()` swaps it.
- **The provider selects nothing.** It publishes the config, the actor and three actions; each hook
- **The provider selects nothing.** It publishes the config, the actor and four actions; each hook
selects its own slice. Never add a field a hook could select.
- **Publish the narrowest type.** `ConnectionSubscription` puts `send` out of reach; `WalletSdk`
narrows `DappSDK` to the methods this package calls.
- **React owns two things:** `lastTx` (`useExecute`) and the `toConnectError` memo (`useConnect`).
Anything else that looks like state belongs in the machine.
- **The machine owns the session; a hook owns what it asked for.** `sdk`, `party`, status and the
last connect error are machine context, never React state. A call's result or in-flight flag
(`lastTx`, a signature) is React state: the session does not depend on it.
- **Import the SDK's types.** A `param as Parameters<…>` cast is a duplicated type: import the real
one from `dapp-sdk` or `core-types`.
- **The picker is `CantonConnectConfig.walletPicker`.** No picker UI in this package; that lives in
Expand All @@ -46,10 +47,14 @@ the session and the transports. Browser-only, and built to stay cheap to delete.

## Bumping `dapp-sdk`

`guardedConnect` rests on two SDK internals no test can pin: that the picker window comes from
`window.open`, and the shape of the `SPLICE_WALLET_PICKER_RESULT` message it reads and posts. Why:
[`architecture/popup-close-guard.md`](architecture/popup-close-guard.md). Serve `dapp/frontend` and
walk all four:
`guardedConnect` and `retireSdk` rest on three SDK internals no test can pin:

- the picker popup is opened through `window.open`
- the shape of the `SPLICE_WALLET_PICKER_RESULT` message
- `connect()` calls `this.walletPicker` only when it reaches the picker, so a swapped one is honored

Why: [`architecture/popup-close-guard.md`](architecture/popup-close-guard.md). Serve `dapp/frontend`
and walk all five:

1. Close the picker without choosing, three times over: the button re-enables each time, and the
next real connect raises exactly one approval prompt.
Expand All @@ -60,6 +65,9 @@ walk all four:
4. Check that `new DappSDK()` still only initializes fields (true on 1.5.1). The machine constructs
one inside a plain `assign`; if construction turns effectful, move the ritual into the provider's
`createSdk` and dispose the abandoned instance on the same transition, never from an effect.
5. Click Connect, then the Cancel that replaces it, fast enough to beat the popup: the button reads
Connect again and no picker window appears. One appearing means `connect()` no longer reads
`walletPicker` off the instance at pick time.

## Layout

Expand Down
24 changes: 14 additions & 10 deletions canton-connect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ types.

[`@partylayer/react`](https://partylayer.xyz) is the alternative, built over its own wallet
adapters. This one wraps Digital Asset's official SDK, the dependency these dApps already carry, and
stays thin enough to delete. How close the result shapes should sit to wagmi's is open in #52.
stays thin enough to delete. How the hook results relate to wagmi's is in
[coming-from-wagmi.md](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/coming-from-wagmi.md).

## Why a state machine

The connection lifecycle looks like four states (idle, connecting, connected,
disconnected) and isn't. The hard part isn't holding state, it's canceling work
when the state that started it is gone: a picker the user walked out of, a lock
when the state that started it is gone: a picker the user abandoned, a lock
that races the account read, a connect asked for mid-disconnect, a wallet that
answers late or never. Handled one at a time these were five separate races
(#76). A state machine folds them into one model: a state's invoked work is
Expand All @@ -36,7 +37,7 @@ switching only once those gaps close. Until then, the machine earns its cost.

| dapp-sdk gap | what it costs us | gone when |
|---|---|---|
| `connect()` can't be aborted; a closed popup hangs it (#49) | `guardedConnect`, `settleAbandonedConnect`, `PickerClosedError`, the `retiring` state | `connect(signal)` truly aborts |
| `connect()` can't be aborted; a closed popup hangs it (#49) | `guardedConnect`, `settleAbandonedConnect`, `PickerClosedError`, the `retiring` state, `retireSdk`'s picker swap | `connect(signal)` truly aborts |
| `init()` caches a rejected promise forever | `retireSdk`, the `retiring` state, `InitFailedError` | `init()` retries after a failure |
| `disconnect()` has no timeout (#105) | `DISCONNECT_TIMEOUT_MS`, and `retireSdk` when it fires | `disconnect()` times out itself |
| lock and wallet-side disconnect are one push | `session.unauthenticated`, party-dropped-on-lock | CIP-0103 separates them (spec, not SDK) |
Expand Down Expand Up @@ -75,7 +76,7 @@ function App() {
}

function Dapp() {
const { connect, isConnecting, isConnected, connectError } = useConnect()
const { connect, isPending, isConnected, error } = useConnect()
const { party } = useParty()
const { isLocked } = useWalletStatus()
const { signMessage } = useSignMessage()
Expand All @@ -85,10 +86,10 @@ function Dapp() {
if (!isConnected) {
return (
<div>
<button onClick={() => connect().catch(() => undefined)} disabled={isConnecting}>
<button onClick={() => connect().catch(() => undefined)} disabled={isPending}>
Connect
</button>
{connectError !== undefined && <p>{connectError.message}</p>}
{error !== undefined && <p>{error.message}</p>}
</div>
)
}
Expand All @@ -103,20 +104,23 @@ function Dapp() {

`connect()` opens the SDK's wallet picker, a popup by default. There is no mode argument: the picker
is what chooses the wallet. Dismissing it rejects with `ConnectCancelledError`, which you filter by
`instanceof`, never by message. Whether `connectError` records it as well depends on which side saw
`instanceof`, never by message. Whether `error` records it as well depends on which side saw
the close, so do not gate on that.

`signMessage`, `execute` and `ledgerApi` refuse with no session, and refuse again while the wallet
reports it is not authenticated; that is `isLocked`, and it happens after a successful connect. The
SDK's status carries one `isConnected` flag, so a lock and a wallet-side disconnect look the same
reports it is not authenticated; that is `isLocked`, and it happens after a successful connect.
`signMessage` and `execute` also refuse with no party, which `ledgerApi` does not need. The
reference gateway also refuses `signMessage` for a local party; `usePartyType().readPartyType()`
tells local from external when you ask. The SDK's status carries one `isConnected` flag, so a lock
and a wallet-side disconnect look the same
here. `useLedger().isReady` covers both, and `useParty().party` is `undefined` for the duration:
gate session content on the party, and use `isLocked` only to explain why it went away.

## Reference

Every hook and every config field is documented in JSDoc, which your editor surfaces at the call
site and which is published at
[docs-canton-dappbooster.vercel.app](https://docs-canton-dappbooster.vercel.app). Start at
[docs.dappbooster.cc](https://docs.dappbooster.cc/). Start at
`CantonConnectProvider` and `CantonConnectConfig`.

## Testing helpers
Expand Down
Loading