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.
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +10 -0
- package/dist/templates.js.map +1 -1
- package/package.json +1 -1
- package/templates/next-ssr/README.md +4 -4
- package/templates/next-ssr/_package.json +1 -1
- package/templates/react-dvp/README.md +134 -0
- package/templates/react-dvp/_gitignore +8 -0
- package/templates/react-dvp/_package.json +26 -0
- package/templates/react-dvp/index.html +12 -0
- package/templates/react-dvp/src/App.css +385 -0
- package/templates/react-dvp/src/App.tsx +110 -0
- package/templates/react-dvp/src/context/DemoContext.tsx +34 -0
- package/templates/react-dvp/src/index.css +23 -0
- package/templates/react-dvp/src/lib/backend.ts +144 -0
- package/templates/react-dvp/src/lib/fixtures.ts +113 -0
- package/templates/react-dvp/src/lib/format.ts +59 -0
- package/templates/react-dvp/src/lib/invalidate.ts +18 -0
- package/templates/react-dvp/src/lib/match.ts +28 -0
- package/templates/react-dvp/src/lib/mutation.ts +17 -0
- package/templates/react-dvp/src/lib/store.ts +332 -0
- package/templates/react-dvp/src/lib/types.ts +35 -0
- package/templates/react-dvp/src/main.tsx +18 -0
- package/templates/react-dvp/src/sections/Holdings.tsx +51 -0
- package/templates/react-dvp/src/sections/MyAllocations.tsx +79 -0
- package/templates/react-dvp/src/sections/Trades.tsx +298 -0
- package/templates/react-dvp/src/ui/primitives.tsx +91 -0
- package/templates/react-dvp/tsconfig.json +20 -0
- package/templates/react-dvp/vite.config.ts +7 -0
- package/templates/react-tokenization/README.md +139 -0
- package/templates/react-tokenization/_gitignore +8 -0
- package/templates/react-tokenization/_package.json +26 -0
- package/templates/react-tokenization/index.html +12 -0
- package/templates/react-tokenization/src/App.css +361 -0
- package/templates/react-tokenization/src/App.tsx +111 -0
- package/templates/react-tokenization/src/context/DemoContext.tsx +34 -0
- package/templates/react-tokenization/src/index.css +23 -0
- package/templates/react-tokenization/src/lib/backend.ts +171 -0
- package/templates/react-tokenization/src/lib/fixtures.ts +134 -0
- package/templates/react-tokenization/src/lib/format.ts +59 -0
- package/templates/react-tokenization/src/lib/invalidate.ts +23 -0
- package/templates/react-tokenization/src/lib/mutation.ts +17 -0
- package/templates/react-tokenization/src/lib/store.ts +219 -0
- package/templates/react-tokenization/src/lib/types.ts +27 -0
- package/templates/react-tokenization/src/main.tsx +18 -0
- package/templates/react-tokenization/src/sections/Allocations.tsx +150 -0
- package/templates/react-tokenization/src/sections/Holdings.tsx +51 -0
- package/templates/react-tokenization/src/sections/Incoming.tsx +107 -0
- package/templates/react-tokenization/src/sections/Issuer.tsx +177 -0
- package/templates/react-tokenization/src/sections/Transfer.tsx +112 -0
- package/templates/react-tokenization/src/ui/primitives.tsx +91 -0
- package/templates/react-tokenization/tsconfig.json +20 -0
- package/templates/react-tokenization/vite.config.ts +7 -0
- package/templates/react-vite/README.md +2 -2
- package/templates/react-vite/_package.json +1 -1
- package/templates/vanilla/README.md +2 -2
- package/templates/vanilla/_package.json +1 -1
- package/templates/vanilla/src/main.ts +2 -2
- package/templates/vue-nuxt-pinia/README.md +3 -3
- package/templates/vue-nuxt-pinia/_package.json +1 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tiny in-memory mutable store for the DvP demo, mirroring the official
|
|
3
|
+
* token-standard trading-app semantics: a trade is an allocation request with two
|
|
4
|
+
* opposite legs; each sender allocates its leg; the venue settles atomically. It
|
|
5
|
+
* stands in for a validator plus a trade app so every hook exercises real loading,
|
|
6
|
+
* success, error, and invalidation without a live ledger.
|
|
7
|
+
*
|
|
8
|
+
* Decimal amounts stay strings throughout; arithmetic goes through the two-decimal
|
|
9
|
+
* helpers in `./format` (documented simplification).
|
|
10
|
+
*
|
|
11
|
+
* Deadlines (`allocateBefore`, `settleBefore`) are ISO 8601 Z strings compared
|
|
12
|
+
* lexicographically against `now()`; that ordering is correct for same-format Z
|
|
13
|
+
* timestamps and needs no date math.
|
|
14
|
+
*/
|
|
15
|
+
import type {
|
|
16
|
+
TokenHoldingRef,
|
|
17
|
+
TokenAllocationRef,
|
|
18
|
+
TokenAllocationRequestRef,
|
|
19
|
+
TokenTransferLeg,
|
|
20
|
+
} from '@partylayer/react/query';
|
|
21
|
+
import { allocationMatchesRequestLeg } from '@partylayer/react/query';
|
|
22
|
+
import type { DemoPartyKey } from './types';
|
|
23
|
+
import {
|
|
24
|
+
PARTIES,
|
|
25
|
+
seedHoldings,
|
|
26
|
+
seedRequests,
|
|
27
|
+
buildTrade,
|
|
28
|
+
DEFAULT_ALLOCATE_BEFORE,
|
|
29
|
+
DEFAULT_SETTLE_BEFORE,
|
|
30
|
+
} from './fixtures';
|
|
31
|
+
import { addAmount, subAmount, cmpAmount } from './format';
|
|
32
|
+
|
|
33
|
+
interface StoreState {
|
|
34
|
+
holdings: Record<DemoPartyKey, TokenHoldingRef[]>;
|
|
35
|
+
requests: TokenAllocationRequestRef[];
|
|
36
|
+
allocations: Record<DemoPartyKey, TokenAllocationRef[]>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let state: StoreState = freshState();
|
|
40
|
+
let cidCounter = 0;
|
|
41
|
+
|
|
42
|
+
function freshState(): StoreState {
|
|
43
|
+
return {
|
|
44
|
+
holdings: seedHoldings(),
|
|
45
|
+
requests: seedRequests(),
|
|
46
|
+
allocations: { venue: [], alice: [], bob: [] },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function nextCid(prefix: string): string {
|
|
51
|
+
cidCounter += 1;
|
|
52
|
+
// The 'gen' namespace keeps generated cids from colliding with the fixed seed cids.
|
|
53
|
+
return prefix + '-gen' + cidCounter.toString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function keyOf(partyId: string): DemoPartyKey | null {
|
|
57
|
+
for (const key of Object.keys(PARTIES) as DemoPartyKey[]) {
|
|
58
|
+
if (PARTIES[key].partyId === partyId) return key;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The current instant, as an ISO 8601 Z string (for deadline comparisons). */
|
|
64
|
+
export function now(): string {
|
|
65
|
+
return new Date().toISOString();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Artificial latency so loading skeletons are visible (200 to 400 ms). */
|
|
69
|
+
export function latency(): Promise<void> {
|
|
70
|
+
const ms = 200 + ((cidCounter * 37) % 200);
|
|
71
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Find the allocation (across parties) that satisfies a request's leg, if any. */
|
|
75
|
+
function findAllocation(requestCid: string, legId: string): TokenAllocationRef | undefined {
|
|
76
|
+
for (const party of Object.keys(state.allocations) as DemoPartyKey[]) {
|
|
77
|
+
for (const alloc of state.allocations[party]) {
|
|
78
|
+
const spec = alloc.allocation.allocation;
|
|
79
|
+
if (spec.settlement.settlementRef.cid === requestCid && spec.transferLegId === legId) {
|
|
80
|
+
return alloc;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function requestByCid(requestCid: string): TokenAllocationRequestRef {
|
|
88
|
+
const req = state.requests.find((r) => r.cid === requestCid);
|
|
89
|
+
if (!req) throw new Error('Trade not found: ' + requestCid);
|
|
90
|
+
return req;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Consume `amount` of an instrument from a party's chosen unlocked holdings (change back). */
|
|
94
|
+
function debit(party: DemoPartyKey, admin: string, id: string, amount: string, inputCids: string[]): void {
|
|
95
|
+
const chosen = state.holdings[party].filter(
|
|
96
|
+
(ref) =>
|
|
97
|
+
inputCids.includes(ref.cid) &&
|
|
98
|
+
!ref.holding.lock &&
|
|
99
|
+
ref.holding.instrumentId.admin === admin &&
|
|
100
|
+
ref.holding.instrumentId.id === id,
|
|
101
|
+
);
|
|
102
|
+
const available = chosen.reduce((sum, ref) => addAmount(sum, ref.holding.amount), '0.00');
|
|
103
|
+
if (cmpAmount(available, amount) < 0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'Insufficient selected holdings: need ' + amount + ' but only ' + available + ' is selected.',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
let remaining = amount;
|
|
109
|
+
const spent = new Set<string>();
|
|
110
|
+
for (const ref of chosen) {
|
|
111
|
+
if (cmpAmount(remaining, '0.00') <= 0) break;
|
|
112
|
+
spent.add(ref.cid);
|
|
113
|
+
if (cmpAmount(ref.holding.amount, remaining) >= 0) {
|
|
114
|
+
const change = subAmount(ref.holding.amount, remaining);
|
|
115
|
+
remaining = '0.00';
|
|
116
|
+
if (cmpAmount(change, '0.00') > 0) {
|
|
117
|
+
state.holdings[party].push({
|
|
118
|
+
cid: nextCid('h-' + party),
|
|
119
|
+
holding: { ...ref.holding, amount: change, lock: undefined },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
remaining = subAmount(remaining, ref.holding.amount);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
state.holdings[party] = state.holdings[party].filter((ref) => !spent.has(ref.cid));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Credit a party with a new unlocked holding of an instrument. */
|
|
130
|
+
function credit(partyId: string, admin: string, id: string, amount: string): void {
|
|
131
|
+
const party = keyOf(partyId);
|
|
132
|
+
if (!party) throw new Error('Unknown party to credit.');
|
|
133
|
+
state.holdings[party].push({
|
|
134
|
+
cid: nextCid('h-' + party),
|
|
135
|
+
holding: { owner: partyId, instrumentId: { admin, id }, amount, lock: undefined, meta: {} },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Release an allocation's backing to its leg sender and drop the allocation. */
|
|
140
|
+
function releaseAllocation(alloc: TokenAllocationRef): void {
|
|
141
|
+
const leg = alloc.allocation.allocation.transferLeg;
|
|
142
|
+
credit(leg.sender, leg.instrumentId.admin, leg.instrumentId.id, leg.amount);
|
|
143
|
+
for (const party of Object.keys(state.allocations) as DemoPartyKey[]) {
|
|
144
|
+
state.allocations[party] = state.allocations[party].filter((a) => a.cid !== alloc.cid);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const demoStore = {
|
|
149
|
+
reset(): void {
|
|
150
|
+
state = freshState();
|
|
151
|
+
cidCounter = 0;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
holdingsOf(party: DemoPartyKey): TokenHoldingRef[] {
|
|
155
|
+
return state.holdings[party].map((ref) => ({ cid: ref.cid, holding: { ...ref.holding } }));
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
requestsPending(): TokenAllocationRequestRef[] {
|
|
159
|
+
return state.requests.map((r) => ({ cid: r.cid, request: r.request }));
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
allocationsOf(party: DemoPartyKey): TokenAllocationRef[] {
|
|
163
|
+
return state.allocations[party].map((a) => ({ cid: a.cid, allocation: a.allocation }));
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/** The leg ids of a request that currently have a matching allocation. */
|
|
167
|
+
matchedLegIds(requestCid: string): string[] {
|
|
168
|
+
const req = requestByCid(requestCid);
|
|
169
|
+
return Object.keys(req.request.transferLegs).filter((legId) => {
|
|
170
|
+
const alloc = findAllocation(requestCid, legId);
|
|
171
|
+
return !!alloc && allocationMatchesRequestLeg(alloc.allocation, req.request, legId);
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/** Unlocked holding cids of a party for a specific instrument (for allocate selection). */
|
|
176
|
+
unlockedCids(party: DemoPartyKey, admin: string, id: string): string[] {
|
|
177
|
+
return state.holdings[party]
|
|
178
|
+
.filter(
|
|
179
|
+
(ref) =>
|
|
180
|
+
!ref.holding.lock &&
|
|
181
|
+
ref.holding.instrumentId.admin === admin &&
|
|
182
|
+
ref.holding.instrumentId.id === id,
|
|
183
|
+
)
|
|
184
|
+
.map((ref) => ref.cid);
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
/** A party's spendable balance of an instrument. */
|
|
188
|
+
balanceOf(party: DemoPartyKey, admin: string, id: string): string {
|
|
189
|
+
return state.holdings[party]
|
|
190
|
+
.filter((ref) => ref.holding.instrumentId.admin === admin && ref.holding.instrumentId.id === id)
|
|
191
|
+
.reduce((sum, ref) => addAmount(sum, ref.holding.amount), '0.00');
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
/** The venue creates a new trade (allocation request) with two opposite legs. */
|
|
195
|
+
createTrade(usdAmount: string, bondAmount: string, allocateBefore?: string, settleBefore?: string): string {
|
|
196
|
+
const n = state.requests.length + cidCounter + 1;
|
|
197
|
+
const cid = nextCid('ar');
|
|
198
|
+
state.requests.push(
|
|
199
|
+
buildTrade(
|
|
200
|
+
'trade-' + n.toString(),
|
|
201
|
+
cid,
|
|
202
|
+
usdAmount,
|
|
203
|
+
bondAmount,
|
|
204
|
+
allocateBefore ?? DEFAULT_ALLOCATE_BEFORE,
|
|
205
|
+
settleBefore ?? DEFAULT_SETTLE_BEFORE,
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
return cid;
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* A sender allocates its leg: debit the selected holdings and create an allocation
|
|
213
|
+
* whose spec is composed FROM the request. Guarded by `allocateBefore`.
|
|
214
|
+
*/
|
|
215
|
+
allocate(party: DemoPartyKey, requestCid: string, legId: string, inputCids: string[]): string {
|
|
216
|
+
const req = requestByCid(requestCid);
|
|
217
|
+
if (now() >= req.request.settlement.allocateBefore) {
|
|
218
|
+
throw new Error('Allocation window has closed (allocateBefore ' + req.request.settlement.allocateBefore + ').');
|
|
219
|
+
}
|
|
220
|
+
const leg = req.request.transferLegs[legId];
|
|
221
|
+
if (!leg) throw new Error('Unknown leg: ' + legId);
|
|
222
|
+
if (leg.sender !== PARTIES[party].partyId) {
|
|
223
|
+
throw new Error('Only the leg sender can allocate this leg.');
|
|
224
|
+
}
|
|
225
|
+
if (findAllocation(requestCid, legId)) {
|
|
226
|
+
throw new Error('This leg is already allocated.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
debit(party, leg.instrumentId.admin, leg.instrumentId.id, leg.amount, inputCids);
|
|
230
|
+
|
|
231
|
+
const cid = nextCid('alloc-' + party);
|
|
232
|
+
state.allocations[party].push({
|
|
233
|
+
cid,
|
|
234
|
+
allocation: {
|
|
235
|
+
allocation: { settlement: req.request.settlement, transferLegId: legId, transferLeg: leg },
|
|
236
|
+
holdingCids: [nextCid('backing-' + party)],
|
|
237
|
+
meta: {},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
return cid;
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/** A sender withdraws its own allocation before `allocateBefore`, releasing the backing. */
|
|
244
|
+
withdrawAllocation(party: DemoPartyKey, allocationCid: string): void {
|
|
245
|
+
const alloc = state.allocations[party].find((a) => a.cid === allocationCid);
|
|
246
|
+
if (!alloc) throw new Error('Allocation not found: ' + allocationCid);
|
|
247
|
+
const req = state.requests.find(
|
|
248
|
+
(r) => r.cid === alloc.allocation.allocation.settlement.settlementRef.cid,
|
|
249
|
+
);
|
|
250
|
+
if (req && now() >= req.request.settlement.allocateBefore) {
|
|
251
|
+
throw new Error('Cannot withdraw after allocateBefore.');
|
|
252
|
+
}
|
|
253
|
+
releaseAllocation(alloc);
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
/** The venue cancels a matched allocation (abort path), releasing the backing early. */
|
|
257
|
+
cancelAllocation(allocationCid: string): void {
|
|
258
|
+
for (const party of Object.keys(state.allocations) as DemoPartyKey[]) {
|
|
259
|
+
const alloc = state.allocations[party].find((a) => a.cid === allocationCid);
|
|
260
|
+
if (alloc) {
|
|
261
|
+
releaseAllocation(alloc);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
throw new Error('Allocation not found: ' + allocationCid);
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The venue settles the trade atomically: verify an expected allocation exists for
|
|
270
|
+
* EVERY leg and `now < settleBefore`, then move all legs in ONE mutation. Throws
|
|
271
|
+
* listing the missing leg ids BEFORE any asset moves if a leg is unallocated or the
|
|
272
|
+
* deadline passed (all or nothing).
|
|
273
|
+
*/
|
|
274
|
+
settle(requestCid: string): void {
|
|
275
|
+
const req = requestByCid(requestCid);
|
|
276
|
+
if (now() >= req.request.settlement.settleBefore) {
|
|
277
|
+
throw new Error('Settlement window has closed (settleBefore ' + req.request.settlement.settleBefore + ').');
|
|
278
|
+
}
|
|
279
|
+
const legIds = Object.keys(req.request.transferLegs);
|
|
280
|
+
const matched: { legId: string; alloc: TokenAllocationRef; leg: TokenTransferLeg }[] = [];
|
|
281
|
+
const missing: string[] = [];
|
|
282
|
+
for (const legId of legIds) {
|
|
283
|
+
const leg = req.request.transferLegs[legId];
|
|
284
|
+
const alloc = findAllocation(requestCid, legId);
|
|
285
|
+
if (alloc && allocationMatchesRequestLeg(alloc.allocation, req.request, legId)) {
|
|
286
|
+
matched.push({ legId, alloc, leg });
|
|
287
|
+
} else {
|
|
288
|
+
missing.push(legId);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (missing.length > 0) {
|
|
292
|
+
throw new Error('Cannot settle: unallocated legs [' + missing.join(', ') + ']. Nothing moved.');
|
|
293
|
+
}
|
|
294
|
+
// All legs matched and in time: move every leg's backing to its receiver.
|
|
295
|
+
for (const { alloc, leg } of matched) {
|
|
296
|
+
credit(leg.receiver, leg.instrumentId.admin, leg.instrumentId.id, leg.amount);
|
|
297
|
+
for (const party of Object.keys(state.allocations) as DemoPartyKey[]) {
|
|
298
|
+
state.allocations[party] = state.allocations[party].filter((a) => a.cid !== alloc.cid);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
state.requests = state.requests.filter((r) => r.cid !== requestCid);
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
/** A leg sender rejects the trade: release its allocations and drop the request. */
|
|
305
|
+
rejectRequest(requestCid: string, actorId: string): void {
|
|
306
|
+
const req = requestByCid(requestCid);
|
|
307
|
+
const senders = Object.values(req.request.transferLegs).map((leg) => leg.sender);
|
|
308
|
+
if (!senders.includes(actorId)) {
|
|
309
|
+
throw new Error('Only a transfer-leg sender can reject this trade.');
|
|
310
|
+
}
|
|
311
|
+
releaseRequestAllocations(requestCid);
|
|
312
|
+
state.requests = state.requests.filter((r) => r.cid !== requestCid);
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/** The venue withdraws the trade: release its allocations and drop the request. */
|
|
316
|
+
withdrawRequest(requestCid: string): void {
|
|
317
|
+
requestByCid(requestCid);
|
|
318
|
+
releaseRequestAllocations(requestCid);
|
|
319
|
+
state.requests = state.requests.filter((r) => r.cid !== requestCid);
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/** Release every allocation backing a request back to its senders. */
|
|
324
|
+
function releaseRequestAllocations(requestCid: string): void {
|
|
325
|
+
const toRelease: TokenAllocationRef[] = [];
|
|
326
|
+
for (const party of Object.keys(state.allocations) as DemoPartyKey[]) {
|
|
327
|
+
for (const alloc of state.allocations[party]) {
|
|
328
|
+
if (alloc.allocation.allocation.settlement.settlementRef.cid === requestCid) toRelease.push(alloc);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
for (const alloc of toRelease) releaseAllocation(alloc);
|
|
332
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App-level view models for the DvP example.
|
|
3
|
+
*
|
|
4
|
+
* The CIP-0056 shapes (holdings, allocations, allocation requests, transfer legs)
|
|
5
|
+
* come straight from `@partylayer/react/query`; only the demo-specific types live
|
|
6
|
+
* here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** The three demo parties: the settlement venue and the two counterparties. */
|
|
10
|
+
export type DemoPartyKey = 'venue' | 'alice' | 'bob';
|
|
11
|
+
|
|
12
|
+
export interface DemoParty {
|
|
13
|
+
key: DemoPartyKey;
|
|
14
|
+
label: string;
|
|
15
|
+
/** The party id string (Daml `Party`). */
|
|
16
|
+
partyId: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A demo instrument: the registry-administered asset a leg transfers. */
|
|
20
|
+
export interface InstrumentConfig {
|
|
21
|
+
admin: string;
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Variables for the venue's atomic settle, exercised through the generic useChoice. */
|
|
27
|
+
export interface SettleTrade {
|
|
28
|
+
requestCid: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Variables for the venue creating a new trade (its own settlement-app choice). */
|
|
32
|
+
export interface CreateTrade {
|
|
33
|
+
usdAmount: string;
|
|
34
|
+
bondAmount: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StrictMode } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
import App from './App';
|
|
5
|
+
import './index.css';
|
|
6
|
+
|
|
7
|
+
// The CIP-0056 hooks (the `@partylayer/react/query` entrypoint) are built on
|
|
8
|
+
// TanStack Query, so they need a QueryClient in context. Create one here and wrap
|
|
9
|
+
// the app, exactly as the react-vite scaffold template does.
|
|
10
|
+
const queryClient = new QueryClient();
|
|
11
|
+
|
|
12
|
+
createRoot(document.getElementById('root')!).render(
|
|
13
|
+
<StrictMode>
|
|
14
|
+
<QueryClientProvider client={queryClient}>
|
|
15
|
+
<App />
|
|
16
|
+
</QueryClientProvider>
|
|
17
|
+
</StrictMode>,
|
|
18
|
+
);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holdings section. Exercises useTokenHoldings (CIP-0056 typed read). Renders each
|
|
3
|
+
* holding with the owner avatar, the amount (string precision preserved), the
|
|
4
|
+
* instrument, and a lock badge when the holding carries a TokenLock.
|
|
5
|
+
*/
|
|
6
|
+
import { useTokenHoldings } from '@partylayer/react/query';
|
|
7
|
+
import { PartyAvatar } from '@partylayer/react';
|
|
8
|
+
import { useDemo, partyKey } from '../context/DemoContext';
|
|
9
|
+
import { Card, AsyncView, Badge } from '../ui/primitives';
|
|
10
|
+
import { formatAmount } from '../lib/format';
|
|
11
|
+
|
|
12
|
+
export function Holdings() {
|
|
13
|
+
const { party, backend } = useDemo();
|
|
14
|
+
const q = useTokenHoldings({
|
|
15
|
+
read: (signal) => backend.readHoldings(party, signal),
|
|
16
|
+
key: partyKey('holdings', party),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Card title="Holdings" hint="useTokenHoldings">
|
|
21
|
+
<AsyncView
|
|
22
|
+
isPending={q.isPending}
|
|
23
|
+
error={q.error}
|
|
24
|
+
data={q.holdings}
|
|
25
|
+
isEmpty={(refs) => refs.length === 0}
|
|
26
|
+
empty="No holdings for this party."
|
|
27
|
+
>
|
|
28
|
+
{(refs) => (
|
|
29
|
+
<ul className="list">
|
|
30
|
+
{refs.map(({ cid, holding: h }) => (
|
|
31
|
+
<li key={cid} className="row">
|
|
32
|
+
<PartyAvatar id={h.owner} size={28} />
|
|
33
|
+
<div className="row-main">
|
|
34
|
+
<div className="row-title">
|
|
35
|
+
{formatAmount(h.amount)} <span className="muted">{h.instrumentId.id}</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="row-sub muted">admin {h.instrumentId.admin}</div>
|
|
38
|
+
</div>
|
|
39
|
+
{h.lock ? (
|
|
40
|
+
<Badge tone="lock" title={'expires ' + (h.lock.expiresAt ?? 'never')}>
|
|
41
|
+
Locked: {h.lock.context ?? 'held'}
|
|
42
|
+
</Badge>
|
|
43
|
+
) : null}
|
|
44
|
+
</li>
|
|
45
|
+
))}
|
|
46
|
+
</ul>
|
|
47
|
+
)}
|
|
48
|
+
</AsyncView>
|
|
49
|
+
</Card>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MyAllocations section. Exercises useTokenAllocations (party-scoped read) and
|
|
3
|
+
* useAllocationAction with the withdraw action. Shows each allocation's settlement
|
|
4
|
+
* ref, leg summary, and backing holding cids, with a Withdraw button (allowed before
|
|
5
|
+
* allocateBefore; the store enforces the guard).
|
|
6
|
+
*/
|
|
7
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
8
|
+
import { useTokenAllocations, useAllocationAction } from '@partylayer/react/query';
|
|
9
|
+
import { TransactionToast } from '@partylayer/react';
|
|
10
|
+
import { useDemo, partyKey } from '../context/DemoContext';
|
|
11
|
+
import { Card, AsyncView, Badge } from '../ui/primitives';
|
|
12
|
+
import { toastStatus } from '../lib/mutation';
|
|
13
|
+
import { invalidateAll } from '../lib/invalidate';
|
|
14
|
+
import { formatAmount } from '../lib/format';
|
|
15
|
+
|
|
16
|
+
export function MyAllocations() {
|
|
17
|
+
const { party, backend } = useDemo();
|
|
18
|
+
const queryClient = useQueryClient();
|
|
19
|
+
|
|
20
|
+
const q = useTokenAllocations({
|
|
21
|
+
read: (signal) => backend.readAllocations(party, signal),
|
|
22
|
+
key: partyKey('allocations', party),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const action = useAllocationAction<{ ok: true }>({
|
|
26
|
+
submit: (request, signal) => backend.submitAllocationAction(request, signal),
|
|
27
|
+
mutation: { onSuccess: () => invalidateAll(queryClient) },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Card title="My allocations" hint="useTokenAllocations + useAllocationAction">
|
|
32
|
+
<AsyncView
|
|
33
|
+
isPending={q.isPending}
|
|
34
|
+
error={q.error}
|
|
35
|
+
data={q.allocations}
|
|
36
|
+
isEmpty={(refs) => refs.length === 0}
|
|
37
|
+
empty="No allocations yet. Allocate a leg from Trades."
|
|
38
|
+
>
|
|
39
|
+
{(refs) => (
|
|
40
|
+
<ul className="list">
|
|
41
|
+
{refs.map(({ cid, allocation: a }) => {
|
|
42
|
+
const leg = a.allocation.transferLeg;
|
|
43
|
+
const settle = a.allocation.settlement;
|
|
44
|
+
return (
|
|
45
|
+
<li key={cid} className="row row-block">
|
|
46
|
+
<div className="row-main">
|
|
47
|
+
<div className="row-title">
|
|
48
|
+
{formatAmount(leg.amount)} <span className="muted">{leg.instrumentId.id}</span>{' '}
|
|
49
|
+
<Badge tone="ok">leg {a.allocation.transferLegId}</Badge>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="row-sub muted">
|
|
52
|
+
to {leg.receiver} · settlement {settle.settlementRef.id}
|
|
53
|
+
</div>
|
|
54
|
+
<div className="row-sub muted">backing {a.holdingCids.join(', ')}</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="row-actions">
|
|
57
|
+
<button
|
|
58
|
+
className="btn btn-ghost btn-small"
|
|
59
|
+
disabled={action.isPending}
|
|
60
|
+
onClick={() => action.submitAction({ allocationCid: cid, action: 'withdraw' })}
|
|
61
|
+
>
|
|
62
|
+
Withdraw
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
</li>
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</ul>
|
|
69
|
+
)}
|
|
70
|
+
</AsyncView>
|
|
71
|
+
|
|
72
|
+
<TransactionToast
|
|
73
|
+
status={toastStatus(action)}
|
|
74
|
+
error={action.error}
|
|
75
|
+
message={action.isSuccess ? 'Allocation withdrawn.' : undefined}
|
|
76
|
+
/>
|
|
77
|
+
</Card>
|
|
78
|
+
);
|
|
79
|
+
}
|