create-partylayer-app 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/templates.d.ts.map +1 -1
  2. package/dist/templates.js +10 -0
  3. package/dist/templates.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/next-ssr/README.md +4 -4
  6. package/templates/next-ssr/_package.json +1 -1
  7. package/templates/react-dvp/README.md +134 -0
  8. package/templates/react-dvp/_gitignore +8 -0
  9. package/templates/react-dvp/_package.json +26 -0
  10. package/templates/react-dvp/index.html +12 -0
  11. package/templates/react-dvp/src/App.css +385 -0
  12. package/templates/react-dvp/src/App.tsx +110 -0
  13. package/templates/react-dvp/src/context/DemoContext.tsx +34 -0
  14. package/templates/react-dvp/src/index.css +23 -0
  15. package/templates/react-dvp/src/lib/backend.ts +144 -0
  16. package/templates/react-dvp/src/lib/fixtures.ts +113 -0
  17. package/templates/react-dvp/src/lib/format.ts +59 -0
  18. package/templates/react-dvp/src/lib/invalidate.ts +18 -0
  19. package/templates/react-dvp/src/lib/match.ts +28 -0
  20. package/templates/react-dvp/src/lib/mutation.ts +17 -0
  21. package/templates/react-dvp/src/lib/store.ts +332 -0
  22. package/templates/react-dvp/src/lib/types.ts +35 -0
  23. package/templates/react-dvp/src/main.tsx +18 -0
  24. package/templates/react-dvp/src/sections/Holdings.tsx +51 -0
  25. package/templates/react-dvp/src/sections/MyAllocations.tsx +79 -0
  26. package/templates/react-dvp/src/sections/Trades.tsx +298 -0
  27. package/templates/react-dvp/src/ui/primitives.tsx +91 -0
  28. package/templates/react-dvp/tsconfig.json +20 -0
  29. package/templates/react-dvp/vite.config.ts +7 -0
  30. package/templates/react-tokenization/README.md +139 -0
  31. package/templates/react-tokenization/_gitignore +8 -0
  32. package/templates/react-tokenization/_package.json +26 -0
  33. package/templates/react-tokenization/index.html +12 -0
  34. package/templates/react-tokenization/src/App.css +361 -0
  35. package/templates/react-tokenization/src/App.tsx +111 -0
  36. package/templates/react-tokenization/src/context/DemoContext.tsx +34 -0
  37. package/templates/react-tokenization/src/index.css +23 -0
  38. package/templates/react-tokenization/src/lib/backend.ts +171 -0
  39. package/templates/react-tokenization/src/lib/fixtures.ts +134 -0
  40. package/templates/react-tokenization/src/lib/format.ts +59 -0
  41. package/templates/react-tokenization/src/lib/invalidate.ts +23 -0
  42. package/templates/react-tokenization/src/lib/mutation.ts +17 -0
  43. package/templates/react-tokenization/src/lib/store.ts +219 -0
  44. package/templates/react-tokenization/src/lib/types.ts +27 -0
  45. package/templates/react-tokenization/src/main.tsx +18 -0
  46. package/templates/react-tokenization/src/sections/Allocations.tsx +150 -0
  47. package/templates/react-tokenization/src/sections/Holdings.tsx +51 -0
  48. package/templates/react-tokenization/src/sections/Incoming.tsx +107 -0
  49. package/templates/react-tokenization/src/sections/Issuer.tsx +177 -0
  50. package/templates/react-tokenization/src/sections/Transfer.tsx +112 -0
  51. package/templates/react-tokenization/src/ui/primitives.tsx +91 -0
  52. package/templates/react-tokenization/tsconfig.json +20 -0
  53. package/templates/react-tokenization/vite.config.ts +7 -0
  54. package/templates/react-vite/README.md +2 -2
  55. package/templates/react-vite/_package.json +1 -1
  56. package/templates/vanilla/README.md +2 -2
  57. package/templates/vanilla/_package.json +1 -1
  58. package/templates/vanilla/src/main.ts +2 -2
  59. package/templates/vue-nuxt-pinia/README.md +3 -3
  60. package/templates/vue-nuxt-pinia/_package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,SAAS,EAAE,YAAY,EAqBnC,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,SAAS,EAAE,YAAY,EA+BnC,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD"}
package/dist/templates.js CHANGED
@@ -4,6 +4,16 @@ export const TEMPLATES = [
4
4
  label: 'React + Vite',
5
5
  hint: 'React 18, Vite, zero-config PartyLayerKit',
6
6
  },
7
+ {
8
+ id: 'react-tokenization',
9
+ label: 'React + Tokenization vertical',
10
+ hint: 'CIP-0056 holdings, transfers, and issuance over a typed demo backend',
11
+ },
12
+ {
13
+ id: 'react-dvp',
14
+ label: 'React + DvP vertical',
15
+ hint: 'Atomic delivery versus payment over CIP-0056 allocations',
16
+ },
7
17
  {
8
18
  id: 'next-ssr',
9
19
  label: 'Next.js (SSR)',
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,2CAA2C;KAClD;IACD;QACE,EAAE,EAAE,UAAU;QACd,KAAK,EAAE,eAAe;QACtB,IAAI,EAAE,mDAAmD;KAC1D;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,yDAAyD;KAChE;IACD;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,sDAAsD;KAC7D;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,2CAA2C;KAClD;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,+BAA+B;QACtC,IAAI,EAAE,sEAAsE;KAC7E;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,sBAAsB;QAC7B,IAAI,EAAE,0DAA0D;KACjE;IACD;QACE,EAAE,EAAE,UAAU;QACd,KAAK,EAAE,eAAe;QACtB,IAAI,EAAE,mDAAmD;KAC1D;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,yDAAyD;KAChE;IACD;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,sDAAsD;KAC7D;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;AAC/C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-partylayer-app",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Scaffold a PartyLayer dApp (Canton wallet integration) in seconds.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  # {{PROJECT_NAME}}
2
2
 
3
- A [PartyLayer](https://partylayer.xyz) dApp — Next.js App Router with **SSR session**.
3
+ A [PartyLayer](https://partylayer.xyz) dApp: Next.js App Router with **SSR session**.
4
4
 
5
5
  ## Develop
6
6
 
@@ -11,8 +11,8 @@ npm run dev
11
11
  ## How SSR session works
12
12
 
13
13
  - **Server** ([lib/session.ts](lib/session.ts)) reads the session cookie via `next/headers` `cookies()` + `@partylayer/session`'s `createCookieStorage` → the connected party is rendered in the **initial HTML** (no disconnected→connected flash). `app/page.tsx` is a Server Component that gates on `getServerSession()`.
14
- - **Client** ([app/providers.tsx](app/providers.tsx)) wraps the app in `PartyLayerKit` with `sessionOptions={{ storage: createCookieStorage() }}` — `document.cookie` (synchronous), the **same** cookie the server reads.
15
- - `next/headers` is imported only in your app (`lib/session.ts`), never in `@partylayer/session` — the library stays framework-agnostic.
14
+ - **Client** ([app/providers.tsx](app/providers.tsx)) wraps the app in `PartyLayerKit` with `sessionOptions={{ storage: createCookieStorage() }}`. This uses `document.cookie` (synchronous), the **same** cookie the server reads.
15
+ - `next/headers` is imported only in your app (`lib/session.ts`), never in `@partylayer/session`. The library stays framework-agnostic.
16
16
 
17
17
  Switch networks by changing `network` in `app/providers.tsx` and `appName` accordingly.
18
18
 
@@ -25,4 +25,4 @@ npm run build && npm start
25
25
  ## Docs
26
26
 
27
27
  - [PartyLayer docs](https://partylayer.xyz/docs/introduction)
28
- - [@partylayer/session — createCookieStorage](https://www.npmjs.com/package/@partylayer/session)
28
+ - [@partylayer/session: createCookieStorage](https://www.npmjs.com/package/@partylayer/session)
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@partylayer/react": "^2.1.0",
14
- "@partylayer/sdk": "^0.15.0",
14
+ "@partylayer/sdk": "^0.16.0",
15
15
  "@partylayer/session": "^1.1.4",
16
16
  "@tanstack/react-query": "^5.0.0",
17
17
  "next": "^14.2.5",
@@ -0,0 +1,134 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A DvP (delivery versus payment) vertical for Canton, scaffolded with
4
+ create-partylayer-app. A single-page dApp where a settlement venue runs an atomic
5
+ two-leg trade over the CIP-0056 (Canton Token Standard) allocation surface. A trade
6
+ IS an allocation request; each counterparty allocates its leg; the venue settles both
7
+ legs in one all-or-nothing step.
8
+
9
+ Everything runs on an in-memory demo backend (typed fixtures behind a small
10
+ `DvpBackend` interface), so every hook is exercised for real (loading, success,
11
+ error, invalidation) without a live ledger. Model 2 throughout: PartyLayer owns none
12
+ of the transport; the dApp supplies every read and submit fetcher, and the hooks type
13
+ the request and wrap the query or mutation.
14
+
15
+ > Reference quality, NOT production audited. This template demonstrates the API in a
16
+ > realistic composition. Review, test, and harden it before using it in production.
17
+
18
+ ## Run it
19
+
20
+ ```bash
21
+ npm create partylayer-app@latest my-app -- --template react-dvp
22
+ cd my-app
23
+ npm install
24
+ npm run dev
25
+ ```
26
+
27
+ ## What is on screen
28
+
29
+ A header with the connect UI, a demo-party switcher (venue / alice / bob), a
30
+ synchronizer picker, and a light/dark toggle (the gold theme family); then three
31
+ section cards:
32
+
33
+ | Section | Hook(s) exercised |
34
+ | :-- | :-- |
35
+ | Trades | `useAllocationRequests`, `useAllocationInstruction`, `useAllocationRequestAction` (counterparty); `useTokenAllocations`, `useChoice`, `useAllocationAction` (venue) |
36
+ | My allocations | `useTokenAllocations` and `useAllocationAction` (withdraw) |
37
+ | Holdings | `useTokenHoldings` |
38
+
39
+ A trade is an allocation request: `settlement { executor: the venue, settlementRef,
40
+ requestedAt, allocateBefore, settleBefore }` plus two opposite `transferLegs` (Alice
41
+ pays DEMO-USD to Bob; Bob delivers DEMO-BOND to Alice). A non-selectable `registry`
42
+ party administers both instruments; the venue (executor) and the registry are
43
+ distinct roles.
44
+
45
+ ## Demo flows
46
+
47
+ 1. Connect and create a trade. As the Venue, click Connect Wallet (the console
48
+ adapter, themed by the gold family). Enter a USD amount and a BOND amount and click
49
+ New trade: a new allocation request appears.
50
+ 2. Alice allocates her USD leg. Switch to Alice. Her leg shows a `CostPreview` before
51
+ confirm; Allocate my leg composes the allocation spec from the request and submits
52
+ it through `useAllocationInstruction`. Her USD holdings drop and a change holding is
53
+ written back.
54
+ 3. Bob allocates his BOND leg. Switch to Bob and allocate his leg the same way.
55
+ 4. The venue settles atomically. Switch to the Venue. The trade shows 2/2 allocated
56
+ and Settle is enabled. Settle moves both legs in one step: Bob receives the USD,
57
+ Alice receives the BOND, and the balances swap.
58
+ 5. Withdraw then settle shows the failure. Allocate a leg, then withdraw it from My
59
+ allocations. The venue's Settle is disabled while a leg is unmatched; the store
60
+ itself throws a descriptive all-or-nothing error (listing the missing leg ids) that
61
+ moves nothing.
62
+ 6. Bob rejects a trade. Switch to Bob and Reject an open trade through
63
+ `useAllocationRequestAction` (action `reject`, actor = Bob). Any allocations for it
64
+ are released and the trade is dropped.
65
+ 7. The venue withdraws a trade. As the Venue, Withdraw trade through
66
+ `useAllocationRequestAction` (action `withdraw`), releasing allocations and dropping
67
+ the trade.
68
+
69
+ The venue can also Cancel allocation on a matched leg (`useAllocationAction`, action
70
+ `cancel`) for the abort path that releases a counterparty's backing early.
71
+
72
+ ## Party-scoped caching
73
+
74
+ Every hook folds the current demo party into its `key` (for example
75
+ `['dvp', 'holdings', party]`), so each party's data is cached independently. Switching
76
+ parties never shows another party's cached data. Invalidation after every write goes
77
+ through the exported `partyLayerKeys` factories (see `src/lib/invalidate.ts`),
78
+ refreshing the trades (`allocationRequests`), allocations (`tokenAllocations`), and
79
+ holdings (`tokenHoldings`).
80
+
81
+ ## Atomic settle
82
+
83
+ The venue's settle mirrors the official trading-app `OTCTrade_Settle`: it verifies an
84
+ expected allocation exists for EVERY leg (a spec match on settlement, transfer leg id,
85
+ and transfer leg) and that `now < settleBefore`, then executes ALL legs in ONE store
86
+ mutation. If any leg is unallocated or the deadline has passed it throws BEFORE any
87
+ asset moves, listing the missing leg ids. This is a dApp-specific trade choice, so the
88
+ UI wires it through the generic `useChoice`: a real venue settles atomically via its
89
+ trade contract's choice whose body exercises `Allocation_ExecuteTransfer` per leg in a
90
+ single transaction, which is exactly why this is `useChoice` and not the
91
+ per-allocation typed hook.
92
+
93
+ The comparator that decides whether an allocation satisfies a request leg is
94
+ `allocationMatchesRequestLeg` from `@partylayer/react/query` (decimal-as-string
95
+ equality without float conversion), used by both the store and the venue view via the
96
+ thin adapter in `src/lib/match.ts`.
97
+
98
+ ## Decimal handling
99
+
100
+ Amounts are decimal strings end to end (never JS numbers), matching the CIP-0056
101
+ `Decimal` convention. The demo store simplifies arithmetic to two decimal places using
102
+ bigint cents (`src/lib/format.ts`); a real dApp handling arbitrary precision should use
103
+ a decimal library.
104
+
105
+ ## Real mode
106
+
107
+ To wire this against a live validator and registry, replace the demo backend's
108
+ fetchers. The hooks and the UI stay exactly as they are.
109
+
110
+ - Discovery. Read the trades a party can act on with an ACS interface-filter query for
111
+ `Splice.Api.Token.AllocationRequestV1:AllocationRequest`, mapped into
112
+ `TokenAllocationRequestRef`. Implementations SHOULD make at least all transfer-leg
113
+ senders observers of the request, which is how wallets discover it.
114
+ - Allocate a leg (`useAllocationInstruction`). Compose the allocation spec from the
115
+ request (`settlement`, `transferLegId`, `transferLeg`), then in the `submit` fetcher
116
+ fetch the registry's allocation-factory (its `choiceContext` and
117
+ `disclosedContracts`), fill `extraArgs.context`, and exercise
118
+ `AllocationFactory_Allocate` with the disclosed contracts. `expectedAdmin` must come
119
+ from a trusted source (e.g. a read against your own participant).
120
+ - Respond to a request (`useAllocationRequestAction`). `AllocationRequest_Reject` (with
121
+ `actor`) and `AllocationRequest_Withdraw` are called with an EMPTY choice context per
122
+ the standard, so the `submit` fetcher exercises directly on the request cid with
123
+ `extraArgs { context: empty, meta }`, no registry fetch.
124
+ - Act on a funded allocation (`useAllocationAction`). Withdraw and cancel go through
125
+ the registry's per-action choice contexts on the allocation cid.
126
+ - Settle. The venue's trade app exercises its own settle choice, whose body runs
127
+ `Allocation_ExecuteTransfer` for every leg in one transaction. The official Splice
128
+ token-standard trading-app example is the canonical reference for this flow.
129
+
130
+ For the shapes these fetchers pass around, `@partylayer/react/query` exports the
131
+ disclosed contract and choice context types (`TokenDisclosedContract`,
132
+ `TokenChoiceContext`, `TokenTransferFactory`, `TokenAllocationFactory`) plus
133
+ `mergeDisclosedContracts` for combining the disclosures of several registry contexts
134
+ into one submission.
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ dist
3
+ dist-ssr
4
+ *.local
5
+ .DS_Store
6
+ .env
7
+ .env.*
8
+ !.env.example
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@partylayer/react": "^2.2.1",
13
+ "@partylayer/sdk": "^0.16.0",
14
+ "@partylayer/adapter-console": "^0.3.15",
15
+ "@tanstack/react-query": "^5.0.0",
16
+ "react": "^18.3.1",
17
+ "react-dom": "^18.3.1"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^18.3.3",
21
+ "@types/react-dom": "^18.3.0",
22
+ "@vitejs/plugin-react": "^4.3.1",
23
+ "typescript": "^5.6.2",
24
+ "vite": "^5.4.2"
25
+ }
26
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{PROJECT_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,385 @@
1
+ /* App chrome. Two token sets follow the light/dark toggle, tuned to the gold
2
+ family so the chrome matches the PartyLayer primitives (which retint from the
3
+ gold theme independently). This is what differentiates DvP from tokenization's teal. */
4
+ .app {
5
+ --bg: #ffffff;
6
+ --surface: #fefce8;
7
+ --card: #ffffff;
8
+ --text: #1c1917;
9
+ --muted: #78716c;
10
+ --border: rgba(28, 25, 23, 0.14);
11
+ --accent: #b45309;
12
+ --accent-contrast: #ffffff;
13
+ --error-bg: #fef2f2;
14
+ --error-text: #b00020;
15
+ --lock-bg: #fff7ed;
16
+ --lock-text: #9a3412;
17
+ min-height: 100vh;
18
+ background: var(--bg);
19
+ color: var(--text);
20
+ transition: background 200ms, color 200ms;
21
+ }
22
+
23
+ .app-dark {
24
+ --bg: #0c0a09;
25
+ --surface: #1c1917;
26
+ --card: #1c1917;
27
+ --text: #fafaf9;
28
+ --muted: #a8a29e;
29
+ --border: rgba(255, 255, 255, 0.1);
30
+ --accent: #fbbf24;
31
+ --accent-contrast: #1c1917;
32
+ --error-bg: #2a0f10;
33
+ --error-text: #fca5a5;
34
+ --lock-bg: #2a1c0f;
35
+ --lock-text: #fbbf24;
36
+ }
37
+
38
+ .topbar {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ gap: 16px;
43
+ flex-wrap: wrap;
44
+ padding: 16px 24px;
45
+ border-bottom: 1px solid var(--border);
46
+ }
47
+
48
+ .brand {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 10px;
52
+ }
53
+ .brand-mark {
54
+ width: 28px;
55
+ height: 28px;
56
+ border-radius: 8px;
57
+ background: var(--accent);
58
+ display: inline-block;
59
+ }
60
+ .brand-title {
61
+ font-weight: 700;
62
+ letter-spacing: -0.01em;
63
+ }
64
+ .brand-sub {
65
+ font-size: 12px;
66
+ color: var(--muted);
67
+ }
68
+
69
+ .topbar-controls {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 12px;
73
+ flex-wrap: wrap;
74
+ }
75
+
76
+ .party-switch {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 6px;
80
+ padding: 4px 8px;
81
+ border: 1px solid var(--border);
82
+ border-radius: 10px;
83
+ }
84
+ .party-switch-label {
85
+ font-size: 11px;
86
+ text-transform: uppercase;
87
+ letter-spacing: 0.06em;
88
+ color: var(--muted);
89
+ margin-right: 2px;
90
+ }
91
+ .party-chip {
92
+ border: none;
93
+ background: transparent;
94
+ color: var(--muted);
95
+ padding: 4px 10px;
96
+ border-radius: 8px;
97
+ cursor: pointer;
98
+ font-size: 13px;
99
+ font-weight: 600;
100
+ }
101
+ .party-chip-on {
102
+ background: var(--accent);
103
+ color: var(--accent-contrast);
104
+ }
105
+
106
+ .acting-line {
107
+ max-width: 1100px;
108
+ margin: 16px auto 0;
109
+ padding: 0 24px;
110
+ color: var(--muted);
111
+ font-size: 14px;
112
+ }
113
+ .acting-line code {
114
+ font-size: 12px;
115
+ }
116
+
117
+ .grid {
118
+ max-width: 1100px;
119
+ margin: 16px auto 0;
120
+ padding: 0 24px 40px;
121
+ display: grid;
122
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
123
+ gap: 20px;
124
+ }
125
+
126
+ .card {
127
+ background: var(--card);
128
+ border: 1px solid var(--border);
129
+ border-radius: 14px;
130
+ overflow: hidden;
131
+ }
132
+ .card-head {
133
+ display: flex;
134
+ align-items: baseline;
135
+ justify-content: space-between;
136
+ gap: 10px;
137
+ padding: 14px 16px;
138
+ border-bottom: 1px solid var(--border);
139
+ }
140
+ .card-head h2 {
141
+ margin: 0;
142
+ font-size: 16px;
143
+ }
144
+ .card-hint {
145
+ font-size: 11px;
146
+ color: var(--muted);
147
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
148
+ }
149
+ .card-body {
150
+ padding: 16px;
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: 14px;
154
+ }
155
+
156
+ .list {
157
+ list-style: none;
158
+ margin: 0;
159
+ padding: 0;
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 10px;
163
+ }
164
+ .row {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 12px;
168
+ padding: 10px 12px;
169
+ border: 1px solid var(--border);
170
+ border-radius: 10px;
171
+ }
172
+ .row-block {
173
+ flex-direction: column;
174
+ align-items: stretch;
175
+ gap: 10px;
176
+ }
177
+ .row-block .row-actions {
178
+ justify-content: flex-start;
179
+ }
180
+ .row-main {
181
+ flex: 1;
182
+ min-width: 0;
183
+ }
184
+ .row-title {
185
+ font-weight: 600;
186
+ }
187
+ .row-sub {
188
+ font-size: 12px;
189
+ word-break: break-all;
190
+ }
191
+ .row-actions {
192
+ display: flex;
193
+ gap: 8px;
194
+ flex-wrap: wrap;
195
+ }
196
+
197
+ .muted {
198
+ color: var(--muted);
199
+ }
200
+
201
+ .badge {
202
+ font-size: 11px;
203
+ font-weight: 600;
204
+ padding: 3px 8px;
205
+ border-radius: 999px;
206
+ white-space: nowrap;
207
+ }
208
+ .badge-neutral {
209
+ background: var(--surface);
210
+ color: var(--muted);
211
+ }
212
+ .badge-lock {
213
+ background: var(--lock-bg);
214
+ color: var(--lock-text);
215
+ }
216
+ .badge-ok {
217
+ background: var(--surface);
218
+ color: var(--accent);
219
+ }
220
+
221
+ .form-grid {
222
+ display: grid;
223
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
224
+ gap: 12px;
225
+ align-items: end;
226
+ }
227
+ .field-action .btn {
228
+ width: 100%;
229
+ }
230
+ .field {
231
+ display: flex;
232
+ flex-direction: column;
233
+ gap: 4px;
234
+ }
235
+ .field-label {
236
+ font-size: 12px;
237
+ color: var(--muted);
238
+ }
239
+ .field-action {
240
+ justify-content: flex-end;
241
+ }
242
+ .field input,
243
+ .field select,
244
+ .topbar-controls select {
245
+ padding: 8px 10px;
246
+ border: 1px solid var(--border);
247
+ border-radius: 8px;
248
+ background: var(--bg);
249
+ color: var(--text);
250
+ font-size: 14px;
251
+ }
252
+
253
+ .btn {
254
+ border: 1px solid transparent;
255
+ border-radius: 8px;
256
+ padding: 8px 14px;
257
+ font-size: 14px;
258
+ font-weight: 600;
259
+ cursor: pointer;
260
+ }
261
+ .btn:disabled {
262
+ opacity: 0.55;
263
+ cursor: not-allowed;
264
+ }
265
+ .btn-primary {
266
+ background: var(--accent);
267
+ color: var(--accent-contrast);
268
+ }
269
+ .btn-ghost {
270
+ background: transparent;
271
+ color: var(--text);
272
+ border-color: var(--border);
273
+ }
274
+ .btn-small {
275
+ padding: 5px 10px;
276
+ font-size: 13px;
277
+ }
278
+
279
+ .review {
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 12px;
283
+ padding-top: 4px;
284
+ }
285
+ .review-line,
286
+ .balance-line,
287
+ .hint-line {
288
+ font-size: 14px;
289
+ }
290
+
291
+ .state {
292
+ padding: 16px;
293
+ border-radius: 10px;
294
+ font-size: 14px;
295
+ display: flex;
296
+ flex-direction: column;
297
+ gap: 4px;
298
+ }
299
+ .state-empty {
300
+ background: var(--surface);
301
+ color: var(--muted);
302
+ }
303
+ .state-error {
304
+ background: var(--error-bg);
305
+ color: var(--error-text);
306
+ }
307
+
308
+ .skeleton {
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 10px;
312
+ }
313
+ .skeleton-row {
314
+ height: 40px;
315
+ border-radius: 10px;
316
+ background: linear-gradient(90deg, var(--surface), var(--border), var(--surface));
317
+ background-size: 200% 100%;
318
+ animation: shimmer 1.2s ease-in-out infinite;
319
+ }
320
+ @keyframes shimmer {
321
+ 0% {
322
+ background-position: 200% 0;
323
+ }
324
+ 100% {
325
+ background-position: -200% 0;
326
+ }
327
+ }
328
+
329
+ .issuer-summary {
330
+ display: flex;
331
+ align-items: flex-start;
332
+ justify-content: space-between;
333
+ gap: 12px;
334
+ flex-wrap: wrap;
335
+ }
336
+ .supply {
337
+ display: flex;
338
+ flex-direction: column;
339
+ align-items: flex-end;
340
+ gap: 2px;
341
+ }
342
+ .freeze {
343
+ display: flex;
344
+ flex-direction: column;
345
+ gap: 10px;
346
+ }
347
+ .alloc-head {
348
+ display: flex;
349
+ align-items: center;
350
+ justify-content: space-between;
351
+ gap: 10px;
352
+ flex-wrap: wrap;
353
+ }
354
+
355
+ .footer {
356
+ max-width: 1100px;
357
+ margin: 0 auto;
358
+ padding: 24px;
359
+ color: var(--muted);
360
+ font-size: 13px;
361
+ border-top: 1px solid var(--border);
362
+ }
363
+
364
+ /* DvP leg layout */
365
+ .leg-actions {
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: 12px;
369
+ width: 100%;
370
+ }
371
+ .leg-action {
372
+ display: flex;
373
+ flex-direction: column;
374
+ gap: 8px;
375
+ padding: 10px;
376
+ border: 1px solid var(--border);
377
+ border-radius: 10px;
378
+ }
379
+ .leg-status {
380
+ display: flex;
381
+ align-items: center;
382
+ justify-content: space-between;
383
+ gap: 10px;
384
+ flex-wrap: wrap;
385
+ }