feat: evolve the vesting dApp to Amulet on Splice LocalNet#85
Draft
fernandomg wants to merge 65 commits into
Draft
feat: evolve the vesting dApp to Amulet on Splice LocalNet#85fernandomg wants to merge 65 commits into
fernandomg wants to merge 65 commits into
Conversation
Imported from cc-vesting-contracts main@000c481 (nonconsuming factory) + vendored splice-amulet 0.1.19.
Add a `scanApi` JSON-RPC method that proxies to the Splice scan service, plus `scanUrl`/`scanHost` config. The proxy uses node:http directly so the Host header is actually sent — undici/fetch silently drops it (forbidden header), which broke nginx vhost routing on LocalNet. Disclosure blobs for DSO-signed AmuletRules/OpenMiningRound come only from scan, so the Amulet backend needs this.
Replace the bare-Canton LiteBackend with AmuletBackend as the sole backend, targeting amulet-vesting on Splice LocalNet via the wallet-service. Every mutating choice carries an AppTransferContext (AmuletRules + latest opened OpenMiningRound) fetched from scan and explicitly disclosed; createVesting selects proposer Amulet holdings and discloses the factory. Key correctness points verified against a live LocalNet ledger: - ACS TemplateFilters must use package-NAME ids (#name:Module:Entity); the JSON Ledger API rejects package-id-qualified ids in filters. - Accept is submitted by the receiver but consumes the proposer's input Amulets, so those must be explicitly disclosed (CONTRACT_NOT_FOUND otherwise). - Pick the highest OpenMiningRound whose opensAt <= now; the top round is often pre-opening and aborts the transfer with deadline-not-exceeded. - The operator (app-provider) is the proposer/funder, so it is connectable.
Re-runnable bootstrap that creates the AmuletVestingFactory, allocates a receiver party, taps Canton Coin, and writes the deploy-specific amulet-parties.json (gitignored, like vesting-lite-parties.json).
Restore the "Fund from" section the lite app left hidden (lite grants were unfunded). Add VestingBackend.availableFunds, implemented in AmuletBackend by summing the party's Amulet holdings, and surface it on the create form: it shows the funder's live CC balance and blocks submitting a grant that exceeds it.
7fff316 to
1f321be
Compare
Wrap the Splice LocalNet dApp flow behind dev-stack actions, matching the existing up/down conventions (port guards, PID/log files, wait helpers). amulet-up preflights LocalNet (never boots it — that is the external canton builder tool), runs the idempotent bootstrap-amulet.mjs, starts a second wallet-service on :3020 pointed at Splice, and serves the dApp on :3012. amulet-down stops the proxy + dApp via PID files (lsof :3020 fallback) and leaves LocalNet running; it avoids a broad tsx-watch pkill so a coexisting bare-Canton :3010 instance survives. Document the new actions in README's dev-stack table and AGENTS.md.
… navigate after create
Dedup the clipboard-copy logic triplicated across GrantCard, GrantTable and WalletControl into lib/clipboard, and the status label/tone mapping shared by the card and detail views into statusPillLabel/statusPillTone.
… ids Address review findings on the money path and platform safety: - ClaimDialog now checks the re-lock floor against the full locked backing (unvested + unclaimed) rather than only the vested slice, matching the contract; partial grant withdraws no longer pass the UI then abort on-ledger. - canClaim enables sub-floor dust withdraws when the grant is fully vested (a full drain leaves a zero remainder), so small balances are no longer stranded. - Snap the last milestone cumulative fraction to exactly 1.0 on encode to match the contract's exact equality check (the UI validator tolerates 1e-9). - selectAmuletCids pulls one extra holding past the target as headroom for holding-fee decay; document that amuletHoldings reports pre-decay initialAmount. - viewAs fetches ledger-end once and shares it across the three ACS reads for a consistent snapshot and fewer round-trips. - Replace crypto.randomUUID with a secure-context-safe uuid() fallback. - shortenParty keeps short fingerprints whole instead of duplicating their ends. - Drop redundant console.warn diagnostics already carried by thrown errors.
- Modal: wire the visible title/description via aria-labelledby/aria-describedby instead of aria-label so the description is announced. - Route-level lazy loading + a Suspense boundary in AppShell; each page is now its own chunk, keeping framer-motion out of the entry bundle. - Add a skip-to-content link and a main landmark id. - Escape now closes the wallet, connect, and dashboard-filter dropdowns. - Label the milestone date/percent inputs for screen readers. - index.html: Open Graph, Twitter, theme-color, and canonical tags. - wallet-service scanApi: pick the http/https transport and default port from the scanUrl scheme so an https scan host is not sent as cleartext to port 80.
Drop five icon components (Dashboard, History, Inbox, PlusCircle, Wallet) and the useWalletStatus hook + its result type, none of which are referenced anywhere after the rewrite.
Add unit tests for the new canClaim/remainderAfter/floorOk re-lock logic, the previously-untested shortenParty (including the short-fingerprint regression), and the secure-context uuid fallback paths.
The frontend architecture doc still described the pre-rewrite mocked app (mockData, Sidebar/RoleToggle, /proposals route, mocked wallet, in-memory ACS). Rewrite it to match the live structure: the backend/ ledger boundary (VestingBackend/AmuletBackend over the wallet-service ledgerApi+scanApi), StealthWallet, the dashboard tabs model, lazy-loaded routes, runtime config from amulet-parties.json, and the re-lock helpers. Drop the stale "mocked src/wallet" note in CLAUDE.md.
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
No related issue. Stacked on top of
feat/vesting-lite— the PR base is that branch, sothis diff is exactly the lite→amulet evolution. Brings the vesting dApp onto real Splice/Amulet:
AmuletBackendbecomes the sole backend, every mutating choice carries an explicitly-disclosedAppTransferContextfetched from the scan service, and grants lock real Canton Coin.Changes
amulet-vestingDaml package + vendored Splice depsscanApi; usesnode:httpso the
Hostheader routes through nginx —fetchsilently drops it)LiteBackendwithAmuletBackend(sole backend).AppTransferContext(AmuletRules + the latest opened OpenMiningRound) is fetched from scan and explicitly
disclosed on every mutating choice;
createVestingselects the proposer's Amulet holdings;the operator (app-provider) is connectable as the proposer/funder
over-funding (
availableFundssums the party's Amulet holdings)scripts/bootstrap-amulet.mjs)Acceptance criteria
Test plan
Automated tests
npm test(56/56) +npm run typecheck+npm run build+npm run lint— all greennpm test(70/70) +npm run lint— greenManual verification
Verified live against a running Splice LocalNet (SV + app-provider), two ways:
cancel→claim consumes); "Fund from" showed the live balance (~235k CC) and blocked over-funding.
No console errors.
Live-verified protocol facts (now encoded in
AmuletBackend):0.0)TemplateFilters require package-NAME ids (#name:Module:Entity), not package-idsAccept(submitted by the receiver) must explicitly disclose the proposer's input AmuletsopensAt <= now(the top round is often pre-opening)Breaking changes
None. Additive —
LiteBackendis removed in favor ofAmuletBackendas the app's sole backend.Checklist
Screenshots
None.