Repository navigation
feat(token-vault): add the token vault example - #275
Merged
Merged
Conversation
…ings Move the holdings read out of useHoldings into src/utils/readHoldings.ts and export it from the main barrel, so an app can read a party other than the connected one, such as a vault's. Each Holding now carries its contractId, which is what a transfer or an allocation names as its input. The sumHoldings and Holding doc blocks, doc-fixtures.d.ts and the tests follow.
Add example-dapps/token-vault as @bootnodedev/canton-example-token-vault, on the same 0.7.0 library ranges as the other examples. The package holds the manifest, the three tsconfigs, vite.config.ts on port 3012 with its test block, .env.example, _gitignore, _pnpm-workspace.yaml and the env schema in src/utils/env.ts. vendor/ carries the two DARs the vault runs on: canton-token-forge.dar, identical to token-vesting's, and canton-token-vault.dar.
The dApp deposits USDX for VSH vault shares and redeems shares for USDX
through the connected CIP-0103 wallet, with the Wallet Gateway registered
as a RemoteAdapter.
- backend/deployment.ts finds the vault through the user's rights on the
templated /v2/users/{user-id}/rights route, matching vault-operator::
only, and reads both InstrumentConfig rows with their disclosure blobs.
Package ids come off each row's templateId.
- backend/requests.ts reads the queue through the AllocationRequest
interface at one offset, scoped to this vault and to the leg the party
sends.
- backend/LedgerBackend.ts taps, proposes, cancels and allocates, picking
inputs off a fresh holdings read.
- providers/Backend keeps token-vesting's retry and generation guard;
providers/Tokens adds the vault's own balance through readHoldings.
- The shell is token-vesting's, renamed to Canton Vault, with the faucet
as the account menu's Tap item and no wrong-network strip.
- scripts/bootstrap-vault.mjs uploads both vendored DARs, then finds or allocates the token-issuer and vault-operator parties through /v2/parties, grants the authenticated user CanActAs on each, and creates the USDX and VSH InstrumentConfig contracts and the Vault only where missing. It warns when what it finds differs from what it would create, and writes no file. It runs as the example's bootstrap script. - scripts/vault-operator.mjs is the vault's backstage signer. It accepts proposals, funds a withdraw's payout leg and settles, acting as the vault party through the JSON Ledger API. Its pure decisions live in scripts/vault-decisions.mjs. - scripts/ledger-api.mjs is the JSON Ledger API client both share, reading CANTON_JSON_API_URL and CANTON_BACKEND_TOKEN from the example's .env. - scripts/operator.sh start|stop runs the operator as stack:up and stack:down, with its pid and log in DEV_STACK_RUN_DIR.
- canton-create/src/apps.ts lists token-vault in EXAMPLES, so the menu and --example token-vault offer it. - canton-create/README.md names it in the example list, both --example command blocks and the dApps table. - The root package.json gains dev:token-vault, which runs dev-stack.sh on example-dapps/token-vault. - knip.json gives the example a workspace whose entry is scripts/*.mjs, since operator.sh reaches the operator where knip cannot see it.
The bootstrap's comment said a participant answers 409 for a package it already holds. A LocalNet takes the identical DAR again with a 200, so the comment now says both, and the 409 branch stays for a participant that refuses instead.
- README.md: what the example is, running it as numbered steps, how a deposit and a withdraw pass between the depositor and the vault operator, and configuring it for another network, with its script table and the rights the wallet's user needs there. - CLAUDE.md: the participant-hosted vault party, the vault-operator hint the bootstrap, the operator and deployment.ts agree on, the two template-id spellings, the InstrumentConfig disclosure read in deployment.ts only, the queue read through AllocationRequest, and the vendor/ rules. AGENTS.md is the shim. - architecture.md: the parts, the committed DARs table, the three interfaces, discovery, the deposit and withdraw sequence diagrams, what keeps the browser's and the operator's views in step, the bootstrap, and the operator's own guards. - The root CLAUDE.md, architecture.md and CONTRIBUTING.md list the example beside the others.
The menu the demo shows lists the examples, so record canton-create/demo.svg again now that it offers Token Vault.
readHoldings and useHoldings returned every Holding the party is a stakeholder of. An instrument admin sees every holding of its token, so readHoldings(ledgerApi, vault) reported the whole VSH supply as the vault's own. Both now keep a view only when its owner is the party read. sumHoldings takes holdings without a contractId again. It never reads the field, so requiring it broke callers that build the list by hand.
…legs queueRows sorted requests by requestId, whose dep- or wd- prefix put every withdraw above every deposit whatever their age. It now sorts on the settlement's requestedAt, newest first. A request leg with no amount became a row asking to fund 0 USDX. It is now dropped like a leg missing any other field.
findParty returned the first vault-operator:: party in /v2/parties page order, while deployment.ts and vault-operator.mjs take the last in sort order. With the hint under two namespaces, the bootstrap could ensure the Vault for one party while the dApp and the operator used the other. findParty now collects every local match and takes the last in sort order.
The operator kept funding payouts and retrying settles for requests past settleBefore, which the ledger refuses, so a payout locked the vault's USDX for nothing. openRequests in vault-decisions.mjs drops them. Each activeContracts call read its own ledger end, so a tick paired requests from one snapshot with allocations from a later one. The tick now reads one offset for its proposals, requests and allocations, and runs discover() again each tick, so a Vault or InstrumentConfig made again while the operator runs no longer leaves it on archived ids.
- VaultBackend and the store take one propose(kind) in place of deposit and withdraw, which only rejoined the kind they split - the store drops loading, which nothing read, and clears its error only when a read succeeds, so a failing poll no longer blinks it - VaultRequest drops requestId, which nothing read - the kit's InstrumentId and tokenKey replace five hand-written id types and comparisons, and requests.ts builds its proposal filters from PROPOSAL_ENTITY - KIND_TEXT and SENT live in pages/Vault/requestKind.ts for both forms - the figures refresh keys on the requests alone and skips the first read after a load, which repeated reads Tokens had just made - the unused explorer setting, Button variants and sizes, Modal props, theme tokens and styles copied from token-vesting are gone
- ledger-api.mjs gains authenticatedUser, which the bootstrap and the operator both read, and the bootstrap creates through submit - the operator reads the user once, and each tick's four sets in parallel before it accepts anything - vaultHoldings reuses viewOf and keeps only holdings the vault owns, as readHoldings does, since the vault sees every VSH holding - allocationFor drops a requestId check the settlement comparison already covers, and the no-op synchronizerId spreads are gone - the bootstrap resolves both packages at once
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ss' into feat/253-token-vault # Conflicts: # architecture.md # canton-dappbooster/src/hooks/useHoldings.ts
- ACS reads send filter and verbose inside eventFormat; Canton 3.6 refuses them on top; - the scripts move to POST preferred-packages and submit-and-wait, which 3.6 still serves; - the tests' fake ledgers refuse the old fields, as a 3.6 participant does; - ports main's 60b4291, which the example predates.
- the other workspaces moved in #270, after this example was copied; - the lockfile drops jsdom 30 and the packages only it used.
- vault README and architecture.md: the browser also reads as the vault party and the issuer; - vault CLAUDE.md and the root one: the operator reads the `InstrumentConfig` too; - vault README: a late cancel fails rather than the wallet reporting the proposal gone; - CONTRIBUTING.md: put back the sentence the vault section split; - name token-vesting where "the token example" now has two candidates.
fernandomg
approved these changes
Nov 13, 2026
fernandomg
left a comment
Member
There was a problem hiding this comment.
All good
I updated with main and solved conflicts (and applied changes as did in 272 to make it work properly with newers versions), while on it CC found inconsistencies in the docs b72befb
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #253
Adds the token vault from
canton-dappbooster-vaultas an example, built on the token-vesting example's shell.Changes
pnpm dev:token-vault, acts as the participant-hosted vault partyvendor/and creates the parties, both instruments and the Vaultcreate-canton-dappboosteroffers the token vault examplereadHoldings, which reads any party's holdings, such as the vault'sDeviations
useHoldingsnow skips holdings the party sees but does not own, which an instrument administrator used to getHoldinggainscontractId, which an allocation names as its inputAcceptance criteria
create-canton-dappboostercan install itTest plan
Automated tests
pnpm installpnpm -C example-dapps/token-vault testandpnpm -C canton-dappbooster testManual verification
pnpm dev:token-vault upunsafewallet-kernelsigning provider and set it as primaryBreaking changes
Holdingnow requirescontractId, so code that builds one by hand must add itsumHoldingsstill takes holdings without itChecklist
Screenshots
token-vault-overview.mp4