Skip to content

refactor: extract wallet-service into its own repo and consume it as a git dependency - #118

Open
gabitoesmiapodo wants to merge 7 commits into
mainfrom
feat/116
Open

refactor: extract wallet-service into its own repo and consume it as a git dependency#118
gabitoesmiapodo wants to merge 7 commits into
mainfrom
feat/116

Conversation

@gabitoesmiapodo

@gabitoesmiapodo gabitoesmiapodo commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #116

wallet-service had three forked copies (here, the devnet droplet, cn-darkpools), which is why the droplet's Canton Coin faucet is broken. It now ships from one canonical repo.

Cross-repo dependency: this branch needs BootNodeDev/canton-wallet-service at tag v0.1.2 (commit 7803269), already pushed and public. The dependency ref in package.json and the commit sha inside the allowBuilds key in pnpm-workspace.yaml have to move together; changing only one breaks the install.

Changes

  • wallet-service installs as a git dependency pinned to v0.1.2 and runs via pnpm exec canton-wallet-service
  • dev-stack.sh drives the external @bootnodedev/canton-barebones LocalNet from its own directory
  • One root .env configures wallet-service, the token mint and the DAR upload
  • Root scripts are the whole local loop: mint-token, build-dar, deploy-dar, bootstrap, app:dev
  • Root pnpm test also runs the scripts/ node tests

Review guide

Start with scripts/dev-stack.sh: it is the whole rewired loop and the only substantive logic in this diff. Then check the dependency ref in package.json against the commit sha inside pnpm-workspace.yaml's allowBuilds key — they must name the same tag, and a mismatch breaks the install.

Skimmable: the doc rewrites in README.md, CLAUDE.md and architecture.md.

Not worth reviewing: the deleted canton-barebones/wallet-service/ files, which moved verbatim to the new repo, and pnpm-lock.yaml. The token-leak fix is in canton-wallet-service v0.1.2, not here — review it there.

Deviations

  • In-repo LocalNet scripts, compose file and the canton:* root scripts are deleted: dead once dev-stack.sh drives the external tool, so "canton-barebones/ otherwise untouched" no longer held
  • Mint and DAR scripts moved to root scripts/, which is where deleting canton-barebones/scripts/ left them
  • Pinned at v0.1.2 rather than the first tag, because review of the extracted repo found three defects
  • v0.1.2 fix 1: the Dockerfile omitted pnpm-workspace.yaml, so docker build failed with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH (the lockfile records the overrides that file holds)
  • v0.1.2 fix 2: the README's install sequence produced no binary, since pnpm blocks a git dependency's prepare by default and no dist/ is committed
  • v0.1.2 fix 3, token exfiltration in ledgerApi: it resolved the caller's resource with new URL(resource, jsonApiUrl), and an absolute or protocol-relative value makes URL discard the base, so the outbound Authorization: Bearer CANTON_BACKEND_TOKEN went to a host the caller chose, plus SSRF to any internal host the service can reach. resource is the dApp's parameter, forwarded by the wallet, so it is attacker-controlled by design. Now refused with -32602 before any request is made; verified at the wire level, the pre-fix build leaked the token to a listener and the fixed build sends nothing

Acceptance criteria

New repo — BootNodeDev/canton-wallet-service

  • Holds the contents of canton-barebones/wallet-service, behaviour unchanged
  • Has its own lockfile from a fresh pnpm install
  • bin entry canton-wallet-service points at dist/server.js, entry carries a shebang
  • prepare script builds it, so installing produces a dist/
  • engines.node and .nvmrc mirror this repo's >=24.15.0
  • Dockerfile flattened to a single COPY package.json
  • @canton-network/wallet-sdk pinned to 1.3.1, @canton-network/core-acs-reader to 1.12.0
  • Its README, CLAUDE.md, AGENTS.md and api-specs travel with it

This repo

  • canton-barebones/wallet-service/ deleted
  • Dependency added as git+ssh://git@github.com/BootNodeDev/canton-wallet-service.git#<ref>
  • pnpm-workspace.yaml package entry removed, SDK overrides no longer needed here
  • biome.json override and the wallet-service:dev script removed
  • wallet-service service block removed from canton-barebones/docker-compose.yaml
  • scripts/dev-stack.sh starts it with pnpm exec canton-wallet-service
  • Root CLAUDE.md tables, architecture.md and root README drop or repoint the wallet-service rows
  • canton-barebones/ otherwise untouched — see Deviations

End-to-end

  • LocalNet up with the external @bootnodedev/canton-barebones tool
  • wallet-service starts from the git dependency, vesting DAR deployed, bootstrap run
  • CIP-0103 browser extension connects and a grant is visible on port 3012
  • No wallet-service directory left in this tree

Test plan

Automated tests

  1. Run pnpm install from the repo root
  2. Run pnpm lint, pnpm typecheck, pnpm build, pnpm test, pnpm knip, pnpm docs:check, pnpm run check:anatomy
  3. Expect all seven green, no warnings
  4. Run git push --dry-run and expect the pre-push hook (typecheck plus gitleaks) to pass

Manual verification

  1. Follow README.md, which this PR rewrites, from Initial setup through Demo dApp
  2. Expect the vesting grants to render on http://localhost:3012 with the wallet connected

Breaking changes

Local workflow only: canton:up, canton:down, canton:health, canton:token, wallet-service:dev and format are gone, and canton-barebones/.env becomes a root .env.

Migration: copy .env.example to .env, scaffold a LocalNet with npx @bootnodedev/canton-barebones init, then drive the stack through ./scripts/dev-stack.sh.

Checklist

  • Self-reviewed my own diff
  • Tests added or updated
  • Docs updated (if applicable)
  • No unrelated changes bundled in

Screenshots

None.

Both belonged to canton-barebones only because the LocalNet did. They are
part of the local loop, not of any one subproject, so they move to
scripts/ alongside the rest of it.

Each resolves .env from its own parent directory, which is what the move
repoints, so neither grows a path argument. deploy-dar.sh now lets a
caller-exported CANTON_BACKEND_TOKEN or CANTON_JSON_API_URL win over
.env, matching mint-token.mjs, and drops the `set -a` that was handing
the whole file (CANTON_AUTH_SECRET included) to curl.

Drops the dapp/daml `deploy` alias, which pointed into the old location.
Closes the in-repo copy of wallet-service. It now ships from
BootNodeDev/canton-wallet-service and arrives as a devDependency pinned
to tag v0.1.1, run on the host with `pnpm exec canton-wallet-service`.

The SDK overrides go with it: the service's own repository pins
@canton-network/wallet-sdk, so pnpm-workspace.yaml no longer carries
them. pnpm will only run a git dependency's `prepare` when it is named
in allowBuilds, and it matches such a dependency by its resolved
codeload tarball id, so that key carries a commit sha and has to be
replaced whenever the ref moves.

Reshapes the root scripts into the whole local loop, in order:
mint-token, build-dar, deploy-dar, bootstrap, app:dev. build-dar drops
its wrapper script for the dapp/daml build it was wrapping, and
bootstrap reads .env so RPC_URL there takes effect.

knip cannot see a dependency reached only through `pnpm exec` in a shell
script, so the service is listed in ignoreDependencies.
The LocalNet is now @bootnodedev/canton-barebones, scaffolded once into a
directory of its own, so dev-stack.sh shells out to it there: a
path-shaped first argument (which also opens the menu), else a second
argument, else CANTON_LOCALNET_DIR, else ~/canton-localnet.

`canton-barebones start` is `docker compose up -d`, so it returns as soon
as the containers exist while Splice takes minutes more to answer. The
old canton:health gate went with the in-repo stack and nothing replaced
it, so the DAR upload raced a participant that was not listening. `up`
now waits for the app-user JSON API before building or deploying.

wallet-service is gated on a 2xx /health rather than a log line another
repo owns, which also catches something unrelated holding 3010 instead of
letting bootstrap fail obscurely against /rpc. Both probe budgets are
wall-clock, not iteration counts: a socket that accepts TCP without
answering costs the full curl timeout per attempt, which made a nominal
300s wait up to three times that.

One .env, at the root, because the service loads dotenv from the
directory it starts in. `up` reads it rather than defaulting the URLs a
second time, so the file every other step resolves config from also
moves the readiness probe.
The compose file, the up/down/health/splice-common scripts and their test
all drove the LocalNet that now lives in its own directory, and the
compose file's only service was the wallet-service image that no longer
builds here. Nothing in the loop calls any of them.

canton-barebones/ itself stays in the tree: what is left of it is config
and a prebuilt DAR, inert, and removing that is its own change.

Also drops the `format` alias from every remaining package. `lint:fix` is
`biome check --write`, which formats and fixes, so `format` was the
weaker of two ways to do the same thing.
CLAUDE.md, architecture.md and the README follow the code: wallet-service
and the LocalNet are external, scripts/ owns the local loop, and there is
one .env at the root.

Two things worth stating rather than leaving to be rediscovered. pnpm
applies `overrides` only in the root running the install, so the
service's own core-acs-reader hold does not travel here — the lock file
alone is what keeps that transitive off the SDK's caret range, and
Renovate's hold is what keeps a refresh from floating it. And a
`pnpm run` alias cannot take arguments, because pnpm forwards `--` into
argv, which is why mint-token bakes its subject in and deploy-dar has to
strip the separator by hand.

canton-barebones/README.md now says the directory is inert and points at
the replacements.
v0.1.2 stops `ledgerApi` resolving a caller-supplied `resource` onto
another origin, which had it forwarding CANTON_BACKEND_TOKEN to any host
the dApp named. The dApp reaches that method through the wallet, so the
value was attacker-controlled by design.

Both halves move together, as they must: the dependency ref and the
commit sha inside the allowBuilds key, which is how pnpm matches a git
dependency for its `prepare` build.
@gabitoesmiapodo gabitoesmiapodo self-assigned this Aug 27, 2026
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
demo.canton-dappbooster Ready Ready Preview Aug 27, 2026 10:51pm
docs.canton-dappbooster Ready Ready Preview Aug 27, 2026 10:51pm

Request Review

The override stayed with the extracted repo, and pnpm resolves a git
dependency's transitives itself rather than reading its lock file, so
this repo lands on 1.18.1 and not the 1.12.0 that repo pins.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Extract wallet-service into its own repo and consume it as a git dependency

1 participant