@chipi-stack/chipi-react 14.7.0 → 14.9.0

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.
@@ -0,0 +1,714 @@
1
+ import { MAX_OE_VALIDITY_WINDOW_SECONDS, buildOutsideExecution, ANY_CALLER_FELT, computeOeHash, buildThresholdEnvelope, serializeExecuteFromOutsideV2Calldata } from '@chipi-stack/backend';
2
+ import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { useState, useEffect, useMemo } from 'react';
5
+
6
+ // src/multisig/action-template.ts
7
+ var FELT_PRIME = BigInt(
8
+ "0x800000000000011000000000000000000000000000000000000000000000001"
9
+ );
10
+ var U128_MASK = (1n << 128n) - 1n;
11
+ function toFelt(v) {
12
+ let n;
13
+ try {
14
+ n = BigInt(typeof v === "string" ? v.trim() : v);
15
+ } catch {
16
+ throw new Error(`Not a felt: ${String(v)}`);
17
+ }
18
+ if (n < 0n || n >= FELT_PRIME) {
19
+ throw new Error(`Felt out of range: ${String(v)}`);
20
+ }
21
+ return "0x" + n.toString(16);
22
+ }
23
+ function u256(v) {
24
+ const n = BigInt(typeof v === "string" ? v.trim() : v);
25
+ if (n < 0n) throw new Error("u256 must be non-negative");
26
+ return ["0x" + (n & U128_MASK).toString(16), "0x" + (n >> 128n).toString(16)];
27
+ }
28
+ function amountToBase(amount, decimals) {
29
+ const t = amount.trim();
30
+ if (!/^\d+(\.\d+)?$/.test(t)) throw new Error(`Invalid amount: ${amount}`);
31
+ const [whole, frac = ""] = t.split(".");
32
+ if (frac.length > decimals) {
33
+ throw new Error(`Too many decimal places (max ${decimals})`);
34
+ }
35
+ const padded = (frac + "0".repeat(decimals)).slice(0, decimals);
36
+ return BigInt(whole) * 10n ** BigInt(decimals) + BigInt(padded || "0");
37
+ }
38
+ function defineActions(templates) {
39
+ const seen = /* @__PURE__ */ new Set();
40
+ for (const t of templates) {
41
+ if (!t.key) throw new Error("ActionTemplate.key is required");
42
+ if (seen.has(t.key)) throw new Error(`Duplicate ActionTemplate key: ${t.key}`);
43
+ seen.add(t.key);
44
+ }
45
+ return templates;
46
+ }
47
+ var VOTE_CHOICES = [
48
+ { label: "Yes", value: "0x1" },
49
+ { label: "No", value: "0x0" },
50
+ { label: "Abstain", value: "0x2" }
51
+ ];
52
+ function voteTemplate(opts) {
53
+ const fields = [];
54
+ if (!opts?.governanceContract) {
55
+ fields.push({ name: "contract", label: "Governance contract", type: "address" });
56
+ }
57
+ fields.push(
58
+ { name: "proposalId", label: "Proposal ID", type: "felt" },
59
+ { name: "choice", label: "Your vote", type: "select", options: VOTE_CHOICES }
60
+ );
61
+ const labelFor = (v) => VOTE_CHOICES.find((c) => c.value === v)?.label ?? v;
62
+ return {
63
+ key: "vote",
64
+ label: "Vote",
65
+ description: "Vote on a governance proposal.",
66
+ fields,
67
+ toCalls: (v) => [
68
+ {
69
+ to: opts?.governanceContract ?? v.contract,
70
+ entrypoint: opts?.entrypoint ?? "vote",
71
+ calldata: [toFelt(v.proposalId), toFelt(v.choice)]
72
+ }
73
+ ],
74
+ title: (v) => `Vote ${labelFor(v.choice)} on Proposal #${v.proposalId}`
75
+ };
76
+ }
77
+ var SN_MAIN_CHAIN_ID = "0x534e5f4d41494e";
78
+ var OE_WINDOW_SECONDS = Number(MAX_OE_VALIDITY_WINDOW_SECONDS);
79
+ var OE_AFTER_SKEW_SECONDS = 60;
80
+ function toOeCalls(calls) {
81
+ return calls.map((c) => ({
82
+ contractAddress: c.to,
83
+ entrypoint: c.entrypoint,
84
+ calldata: c.calldata
85
+ }));
86
+ }
87
+ function serializeEnvelope(env) {
88
+ return env.map((f) => "0x" + f.toString(16)).join(",");
89
+ }
90
+ function deserializeEnvelope(s) {
91
+ return s.split(",").filter(Boolean).map((f) => BigInt(f));
92
+ }
93
+ function newOeNonce(nowMs, rand) {
94
+ return BigInt(nowMs) << 24n | BigInt(Math.floor(rand * 16777215));
95
+ }
96
+ function deriveExecuteAfter(oeExecuteBefore) {
97
+ return BigInt(oeExecuteBefore) - BigInt(OE_WINDOW_SECONDS);
98
+ }
99
+ async function buildActionProposal(params) {
100
+ const nowMs = params.nowMs ?? Date.now();
101
+ const nonce = newOeNonce(nowMs, params.rand ?? Math.random());
102
+ const executeAfter = BigInt(Math.floor(nowMs / 1e3) - OE_AFTER_SKEW_SECONDS);
103
+ const executeBefore = executeAfter + BigInt(OE_WINDOW_SECONDS);
104
+ const oe = buildOutsideExecution({
105
+ nonce,
106
+ calls: toOeCalls(params.calls),
107
+ caller: ANY_CALLER_FELT,
108
+ executeAfter,
109
+ executeBefore
110
+ });
111
+ const messageHash = computeOeHash(oe, params.walletAddress, params.chainId ?? SN_MAIN_CHAIN_ID);
112
+ const inner = await params.sign({
113
+ messageHash,
114
+ ownerIndex: params.ownerIndex ?? 0n
115
+ });
116
+ return {
117
+ oeMessageHash: "0x" + messageHash.toString(16),
118
+ oeNonce: "0x" + nonce.toString(16),
119
+ oeExecuteBefore: executeBefore.toString(),
120
+ proposerSignature: serializeEnvelope(inner)
121
+ };
122
+ }
123
+ async function signActionApproval(params) {
124
+ const inner = await params.sign({
125
+ messageHash: BigInt(params.oeMessageHash),
126
+ ownerIndex: params.ownerIndex
127
+ });
128
+ return serializeEnvelope(inner);
129
+ }
130
+ function assembleActionExecuteCalldata(params) {
131
+ const oe = buildOutsideExecution({
132
+ nonce: BigInt(params.oeNonce),
133
+ calls: toOeCalls(params.calls),
134
+ caller: ANY_CALLER_FELT,
135
+ executeAfter: deriveExecuteAfter(params.oeExecuteBefore),
136
+ executeBefore: BigInt(params.oeExecuteBefore)
137
+ });
138
+ const inner = params.signatures.map(deserializeEnvelope);
139
+ if (inner.length < Math.max(1, params.requiredApprovals)) {
140
+ throw new Error(
141
+ `Not enough signatures: have ${inner.length}, need ${params.requiredApprovals}.`
142
+ );
143
+ }
144
+ const signature = params.requiredApprovals <= 1 ? inner[0] : buildThresholdEnvelope({ innerEnvelopes: inner });
145
+ return serializeExecuteFromOutsideV2Calldata({
146
+ outsideExecution: oe,
147
+ signature
148
+ });
149
+ }
150
+
151
+ // src/multisig/proposals.ts
152
+ async function createProposal(params) {
153
+ const calls = params.template.toCalls(params.values);
154
+ if (calls.length === 0) {
155
+ throw new Error("Template produced no calls");
156
+ }
157
+ const title = params.template.title(params.values);
158
+ const meta = params.template.meta?.(params.values);
159
+ const proposerOwnerId = params.signer.proposerOwnerIndex ?? 0;
160
+ const proposal = await buildActionProposal({
161
+ walletAddress: params.walletAddress,
162
+ calls,
163
+ sign: params.signer.sign,
164
+ ownerIndex: BigInt(proposerOwnerId),
165
+ chainId: params.chainId,
166
+ nowMs: params.nowMs,
167
+ rand: params.rand
168
+ });
169
+ return {
170
+ proposerOwnerId,
171
+ recipient: calls[0].to,
172
+ tokenAddress: calls[0].to,
173
+ amount: "0",
174
+ title,
175
+ calls,
176
+ ...meta ? { meta } : {},
177
+ ...params.template.risk ? { risk: params.template.risk } : {},
178
+ oeMessageHash: proposal.oeMessageHash,
179
+ oeNonce: proposal.oeNonce,
180
+ oeExecuteBefore: proposal.oeExecuteBefore,
181
+ requiredApprovals: params.requiredApprovals,
182
+ proposerSignature: proposal.proposerSignature,
183
+ proposerSignerKind: params.signer.signerKind
184
+ };
185
+ }
186
+ async function buildApproval(params) {
187
+ const ownerId = await params.signer.resolveOwnerIndex(params.walletAddress);
188
+ const signature = await signActionApproval({
189
+ oeMessageHash: params.proposal.oeMessageHash,
190
+ ownerIndex: BigInt(ownerId),
191
+ sign: params.signer.sign
192
+ });
193
+ return { ownerId, signature, signerKind: params.signer.signerKind };
194
+ }
195
+ function buildExecuteCalldata(proposal) {
196
+ if (!proposal.calls || proposal.calls.length === 0) {
197
+ throw new Error("Proposal has no calls to execute");
198
+ }
199
+ if (!proposal.oeNonce || !proposal.oeExecuteBefore) {
200
+ throw new Error("Proposal is missing OE params (nonce / executeBefore)");
201
+ }
202
+ const need = Math.max(1, proposal.requiredApprovals);
203
+ const signatures = [...proposal.signatures].sort((a, b) => a.ownerId - b.ownerId).slice(0, need).map((s) => s.signature);
204
+ return assembleActionExecuteCalldata({
205
+ calls: proposal.calls,
206
+ oeNonce: proposal.oeNonce,
207
+ oeExecuteBefore: proposal.oeExecuteBefore,
208
+ signatures,
209
+ requiredApprovals: proposal.requiredApprovals
210
+ });
211
+ }
212
+ var proposalsKey = (walletAddress) => ["chipi-multisig-proposals", walletAddress];
213
+ function useProposeAction(config) {
214
+ const qc = useQueryClient();
215
+ return useMutation({
216
+ mutationFn: async (input) => {
217
+ const requiredApprovals = await config.getRequiredApprovals();
218
+ const body = await createProposal({
219
+ template: input.template,
220
+ values: input.values,
221
+ walletAddress: config.walletAddress,
222
+ requiredApprovals,
223
+ signer: config.signer,
224
+ chainId: config.chainId
225
+ });
226
+ return config.transport.propose(body);
227
+ },
228
+ onSuccess: () => qc.invalidateQueries({
229
+ queryKey: proposalsKey(config.walletAddress)
230
+ })
231
+ });
232
+ }
233
+ function useTreasuryProposals(config) {
234
+ const qc = useQueryClient();
235
+ const invalidate = () => qc.invalidateQueries({ queryKey: proposalsKey(config.walletAddress) });
236
+ const proposals = useQuery({
237
+ queryKey: proposalsKey(config.walletAddress),
238
+ staleTime: 30 * 1e3,
239
+ queryFn: () => config.transport.list()
240
+ });
241
+ const approve = useMutation({
242
+ mutationFn: async (proposal) => {
243
+ const body = await buildApproval({
244
+ proposal,
245
+ walletAddress: config.walletAddress,
246
+ signer: config.signer
247
+ });
248
+ return config.transport.sign(proposal.id, body);
249
+ },
250
+ onSuccess: invalidate
251
+ });
252
+ const execute = useMutation({
253
+ mutationFn: (proposal) => config.transport.execute(proposal.id, buildExecuteCalldata(proposal)),
254
+ onSuccess: invalidate
255
+ });
256
+ return { proposals, approve, execute };
257
+ }
258
+
259
+ // src/multisig/components/theme.ts
260
+ var DEFAULT_THEME = {
261
+ accent: "#4f46e5",
262
+ accentText: "#ffffff",
263
+ surface: "#fffdf8",
264
+ surfaceMuted: "#f4f1ea",
265
+ text: "#111111",
266
+ textMuted: "#6b7280",
267
+ danger: "#dc2626",
268
+ success: "#16a34a",
269
+ border: "#111111",
270
+ radius: "12px",
271
+ shadow: "4px 4px 0 0 #111111",
272
+ font: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif"
273
+ };
274
+ function themeVars(theme) {
275
+ const t = { ...DEFAULT_THEME, ...theme };
276
+ return {
277
+ "--cgov-accent": t.accent,
278
+ "--cgov-accent-text": t.accentText,
279
+ "--cgov-surface": t.surface,
280
+ "--cgov-surface-muted": t.surfaceMuted,
281
+ "--cgov-text": t.text,
282
+ "--cgov-text-muted": t.textMuted,
283
+ "--cgov-danger": t.danger,
284
+ "--cgov-success": t.success,
285
+ "--cgov-border": t.border,
286
+ "--cgov-radius": t.radius,
287
+ "--cgov-shadow": t.shadow,
288
+ "--cgov-font": t.font
289
+ };
290
+ }
291
+ var STYLE_ID = "chipi-gov-styles";
292
+ var STYLE_TEXT = `
293
+ .cgov-root{font-family:var(--cgov-font);color:var(--cgov-text);box-sizing:border-box}
294
+ .cgov-root *,.cgov-root *::before,.cgov-root *::after{box-sizing:inherit}
295
+ .cgov-card{background:var(--cgov-surface);border:2px solid var(--cgov-border);border-radius:var(--cgov-radius);box-shadow:var(--cgov-shadow)}
296
+ .cgov-btn{display:inline-flex;align-items:center;gap:.5em;font:inherit;font-weight:600;cursor:pointer;border:2px solid var(--cgov-border);border-radius:calc(var(--cgov-radius) * .75);padding:.5em 1em;background:var(--cgov-surface);color:var(--cgov-text);transition:transform .06s ease}
297
+ .cgov-btn:hover:not(:disabled){transform:translate(-1px,-1px)}
298
+ .cgov-btn:active:not(:disabled){transform:translate(1px,1px)}
299
+ .cgov-btn:disabled{opacity:.5;cursor:not-allowed}
300
+ .cgov-btn-primary{background:var(--cgov-accent);color:var(--cgov-accent-text)}
301
+ .cgov-btn-sm{font-size:.8125rem;padding:.35em .8em}
302
+ .cgov-input,.cgov-select{font:inherit;width:100%;padding:.5em .75em;border:2px solid var(--cgov-border);border-radius:calc(var(--cgov-radius) * .6);background:var(--cgov-surface-muted);color:var(--cgov-text)}
303
+ .cgov-input:focus,.cgov-select:focus{outline:3px solid color-mix(in srgb, var(--cgov-accent) 40%, transparent);outline-offset:1px}
304
+ .cgov-label{display:block;font-size:.8125rem;font-weight:600;margin:0 0 .3em}
305
+ .cgov-help{font-size:.75rem;color:var(--cgov-text-muted);margin:.25em 0 0}
306
+ .cgov-field{margin-bottom:.9rem}
307
+ .cgov-field-error{font-size:.75rem;color:var(--cgov-danger);margin:.25em 0 0}
308
+ .cgov-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:flex-end;justify-content:center;z-index:2147483000;padding:1rem}
309
+ @media (min-width:640px){.cgov-overlay{align-items:center}}
310
+ .cgov-dialog{width:100%;max-width:28rem;max-height:calc(100vh - 2rem);overflow-y:auto;padding:1.25rem}
311
+ .cgov-dialog-title{font-size:1.05rem;font-weight:700;margin:0 0 .25rem}
312
+ .cgov-dialog-desc{font-size:.8125rem;color:var(--cgov-text-muted);margin:0 0 1rem}
313
+ .cgov-preview{background:var(--cgov-surface-muted);border:2px dashed var(--cgov-border);border-radius:calc(var(--cgov-radius) * .6);padding:.75rem;margin:.25rem 0 1rem}
314
+ .cgov-preview-title{font-weight:700;font-size:.9375rem;margin:0}
315
+ .cgov-preview-rule{font-size:.75rem;color:var(--cgov-text-muted);margin:.35em 0 0}
316
+ .cgov-preview-call{font-family:ui-monospace,monospace;font-size:.6875rem;color:var(--cgov-text-muted);margin:.35em 0 0;word-break:break-all}
317
+ .cgov-tabs{display:flex;gap:.4rem;flex-wrap:wrap;margin:0 0 1rem}
318
+ .cgov-tab{font:inherit;font-size:.8125rem;font-weight:600;padding:.35em .8em;cursor:pointer;border:2px solid var(--cgov-border);border-radius:999px;background:var(--cgov-surface)}
319
+ .cgov-tab[aria-selected="true"]{background:var(--cgov-accent);color:var(--cgov-accent-text)}
320
+ .cgov-row{display:flex;align-items:center;gap:.75rem;padding:.75rem;border-bottom:2px solid color-mix(in srgb, var(--cgov-border) 15%, transparent)}
321
+ .cgov-row:last-child{border-bottom:none}
322
+ .cgov-row-main{flex:1;min-width:0}
323
+ .cgov-row-title{font-weight:600;font-size:.9375rem;margin:0;overflow:hidden;text-overflow:ellipsis}
324
+ .cgov-row-sub{font-size:.75rem;color:var(--cgov-text-muted);margin:.15em 0 0;display:flex;gap:.5em;align-items:center;flex-wrap:wrap}
325
+ .cgov-chip{display:inline-block;font-size:.6875rem;font-weight:700;padding:.15em .6em;border:1.5px solid var(--cgov-border);border-radius:999px;background:var(--cgov-surface-muted)}
326
+ .cgov-chip-success{background:color-mix(in srgb, var(--cgov-success) 18%, transparent)}
327
+ .cgov-chip-accent{background:color-mix(in srgb, var(--cgov-accent) 18%, transparent)}
328
+ .cgov-empty{padding:1.5rem;text-align:center;color:var(--cgov-text-muted);font-size:.875rem}
329
+ .cgov-error{padding:.6rem .75rem;border:2px solid var(--cgov-danger);border-radius:calc(var(--cgov-radius) * .6);background:color-mix(in srgb, var(--cgov-danger) 8%, transparent);color:var(--cgov-danger);font-size:.8125rem;margin:.5rem 0}
330
+ .cgov-group-label{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--cgov-text-muted);margin:1rem 0 .35rem;padding:0 .25rem}
331
+ .cgov-group-label:first-child{margin-top:0}
332
+ .cgov-spinner{display:inline-block;width:.9em;height:.9em;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:cgov-spin .7s linear infinite}
333
+ @keyframes cgov-spin{to{transform:rotate(360deg)}}
334
+ `;
335
+ function injectGovStyles() {
336
+ const doc = globalThis.document;
337
+ if (!doc) return;
338
+ if (doc.getElementById(STYLE_ID)) return;
339
+ const el = doc.createElement("style");
340
+ el.id = STYLE_ID;
341
+ el.textContent = STYLE_TEXT;
342
+ doc.head.appendChild(el);
343
+ }
344
+ function fieldSyntaxError(field, raw) {
345
+ if (raw.trim() === "") return `${field.label} is required`;
346
+ const v = raw.trim();
347
+ switch (field.type) {
348
+ case "address":
349
+ return /^0x[0-9a-fA-F]{1,64}$/.test(v) ? null : `${field.label} must be a 0x address`;
350
+ case "felt":
351
+ return /^(0x[0-9a-fA-F]+|\d+)$/.test(v) ? null : `${field.label} must be a felt (0x-hex or decimal)`;
352
+ case "amount":
353
+ return /^\d+(\.\d+)?$/.test(v) ? null : `${field.label} must be a positive amount`;
354
+ case "number":
355
+ case "bps":
356
+ case "days":
357
+ return /^\d+$/.test(v) ? null : `${field.label} must be a whole number`;
358
+ default:
359
+ return null;
360
+ }
361
+ }
362
+ function FieldInput(props) {
363
+ const { field, value, onChange, error, disabled } = props;
364
+ const id = `cgov-field-${field.name}`;
365
+ return /* @__PURE__ */ jsxs("div", { className: "cgov-field", children: [
366
+ /* @__PURE__ */ jsx("label", { className: "cgov-label", htmlFor: id, children: field.label }),
367
+ field.type === "select" ? /* @__PURE__ */ jsxs(
368
+ "select",
369
+ {
370
+ id,
371
+ className: "cgov-select",
372
+ value,
373
+ disabled,
374
+ onChange: (e) => onChange(e.target.value),
375
+ children: [
376
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Choose\u2026" }),
377
+ field.options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
378
+ ]
379
+ }
380
+ ) : /* @__PURE__ */ jsx(
381
+ "input",
382
+ {
383
+ id,
384
+ className: "cgov-input",
385
+ value,
386
+ disabled,
387
+ inputMode: field.type === "amount" || field.type === "number" || field.type === "bps" || field.type === "days" ? "decimal" : "text",
388
+ placeholder: field.type === "address" ? "0x\u2026" : field.type === "amount" ? "0.00" : void 0,
389
+ onChange: (e) => onChange(e.target.value)
390
+ }
391
+ ),
392
+ "help" in field && field.help ? /* @__PURE__ */ jsx("p", { className: "cgov-help", children: field.help }) : null,
393
+ error ? /* @__PURE__ */ jsx("p", { className: "cgov-field-error", children: error }) : null
394
+ ] });
395
+ }
396
+ function buildPreview(template, values) {
397
+ for (const f of template.fields) {
398
+ const err = fieldSyntaxError(f, values[f.name] ?? "");
399
+ if (err) return { error: err };
400
+ }
401
+ try {
402
+ return { title: template.title(values), calls: template.toCalls(values) };
403
+ } catch (e) {
404
+ return { error: e instanceof Error ? e.message : "Invalid values" };
405
+ }
406
+ }
407
+ function ProposeAction(props) {
408
+ const [open, setOpen] = useState(false);
409
+ const [templateKey, setTemplateKey] = useState(props.preselect ?? props.actions[0]?.key ?? "");
410
+ const [values, setValues] = useState({});
411
+ const [touched, setTouched] = useState({});
412
+ const [requiredApprovals, setRequiredApprovals] = useState(null);
413
+ const [done, setDone] = useState(null);
414
+ useEffect(injectGovStyles, []);
415
+ const template = props.actions.find((t) => t.key === templateKey) ?? props.actions[0];
416
+ const propose = useProposeAction({
417
+ walletAddress: props.walletAddress,
418
+ transport: props.transport,
419
+ signer: props.signer,
420
+ getRequiredApprovals: props.getRequiredApprovals,
421
+ chainId: props.chainId
422
+ });
423
+ useEffect(() => {
424
+ if (!open) return;
425
+ let on = true;
426
+ props.getRequiredApprovals().then(
427
+ (n) => on && setRequiredApprovals(n),
428
+ () => on && setRequiredApprovals(null)
429
+ );
430
+ return () => {
431
+ on = false;
432
+ };
433
+ }, [open]);
434
+ const preview = useMemo(
435
+ () => template ? buildPreview(template, values) : { error: "No action templates" },
436
+ [template, values]
437
+ );
438
+ const reset = () => {
439
+ setValues({});
440
+ setTouched({});
441
+ setDone(null);
442
+ propose.reset();
443
+ };
444
+ const close = () => {
445
+ setOpen(false);
446
+ reset();
447
+ };
448
+ const submit = async () => {
449
+ if (!template || "error" in preview) return;
450
+ try {
451
+ const proposal = await propose.mutateAsync({ template, values });
452
+ setDone(proposal);
453
+ props.onProposed?.(proposal);
454
+ } catch {
455
+ }
456
+ };
457
+ return /* @__PURE__ */ jsxs("div", { className: "cgov-root", style: themeVars(props.theme), children: [
458
+ props.trigger ? props.trigger(() => setOpen(true)) : /* @__PURE__ */ jsx("button", { type: "button", className: "cgov-btn cgov-btn-primary", onClick: () => setOpen(true), children: "Propose action" }),
459
+ open ? /* @__PURE__ */ jsx("div", { className: "cgov-overlay", role: "presentation", onClick: close, children: /* @__PURE__ */ jsx(
460
+ "div",
461
+ {
462
+ className: "cgov-card cgov-dialog cgov-root",
463
+ style: themeVars(props.theme),
464
+ role: "dialog",
465
+ "aria-modal": "true",
466
+ "aria-label": done ? "Proposal created" : "Propose an action",
467
+ onClick: (e) => e.stopPropagation(),
468
+ children: done ? /* @__PURE__ */ jsxs(Fragment, { children: [
469
+ /* @__PURE__ */ jsx("h2", { className: "cgov-dialog-title", children: "Proposed \u2713" }),
470
+ /* @__PURE__ */ jsxs("p", { className: "cgov-dialog-desc", children: [
471
+ done.title ?? "Your action",
472
+ " \u2014",
473
+ " ",
474
+ done.status === "APPROVED" ? "approved and ready to execute." : `needs ${done.requiredApprovals} approval${done.requiredApprovals === 1 ? "" : "s"}.`
475
+ ] }),
476
+ /* @__PURE__ */ jsx("button", { type: "button", className: "cgov-btn cgov-btn-primary", onClick: close, children: "Done" })
477
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
478
+ /* @__PURE__ */ jsx("h2", { className: "cgov-dialog-title", children: "Propose an action" }),
479
+ /* @__PURE__ */ jsx("p", { className: "cgov-dialog-desc", children: "Your team approves before anything moves on chain." }),
480
+ props.actions.length > 1 ? /* @__PURE__ */ jsx("div", { className: "cgov-tabs", role: "tablist", "aria-label": "Action type", children: props.actions.map((t) => /* @__PURE__ */ jsx(
481
+ "button",
482
+ {
483
+ type: "button",
484
+ role: "tab",
485
+ "aria-selected": t.key === template?.key,
486
+ className: "cgov-tab",
487
+ onClick: () => {
488
+ setTemplateKey(t.key);
489
+ reset();
490
+ },
491
+ children: t.label
492
+ },
493
+ t.key
494
+ )) }) : null,
495
+ template?.description ? /* @__PURE__ */ jsx("p", { className: "cgov-help", style: { marginBottom: "0.9rem" }, children: template.description }) : null,
496
+ template?.fields.map((f) => /* @__PURE__ */ jsx(
497
+ FieldInput,
498
+ {
499
+ field: f,
500
+ value: values[f.name] ?? "",
501
+ disabled: propose.isPending,
502
+ error: touched[f.name] ? fieldSyntaxError(f, values[f.name] ?? "") : null,
503
+ onChange: (raw) => {
504
+ setValues((v) => ({ ...v, [f.name]: raw }));
505
+ setTouched((t) => ({ ...t, [f.name]: true }));
506
+ }
507
+ },
508
+ f.name
509
+ )),
510
+ /* @__PURE__ */ jsx("div", { className: "cgov-preview", "aria-live": "polite", children: "error" in preview ? /* @__PURE__ */ jsx("p", { className: "cgov-preview-rule", children: "What this does \u2014 fill in the fields above." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
511
+ /* @__PURE__ */ jsx("p", { className: "cgov-preview-title", children: preview.title }),
512
+ preview.calls.map((c, i) => /* @__PURE__ */ jsxs("p", { className: "cgov-preview-call", children: [
513
+ c.entrypoint,
514
+ "(",
515
+ c.calldata.join(", "),
516
+ ") \u2192 ",
517
+ c.to.slice(0, 10),
518
+ "\u2026"
519
+ ] }, i)),
520
+ /* @__PURE__ */ jsx("p", { className: "cgov-preview-rule", children: requiredApprovals != null ? `Needs ${requiredApprovals} approval${requiredApprovals === 1 ? "" : "s"} before it executes.` : "Approval rule loads from your wallet." })
521
+ ] }) }),
522
+ propose.error ? /* @__PURE__ */ jsx("div", { className: "cgov-error", role: "alert", children: propose.error instanceof Error ? propose.error.message : "Could not create the proposal. Try again." }) : null,
523
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", justifyContent: "flex-end" }, children: [
524
+ /* @__PURE__ */ jsx(
525
+ "button",
526
+ {
527
+ type: "button",
528
+ className: "cgov-btn",
529
+ onClick: close,
530
+ disabled: propose.isPending,
531
+ children: "Cancel"
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsx(
535
+ "button",
536
+ {
537
+ type: "button",
538
+ className: "cgov-btn cgov-btn-primary",
539
+ disabled: "error" in preview || propose.isPending,
540
+ onClick: submit,
541
+ children: propose.isPending ? /* @__PURE__ */ jsxs(Fragment, { children: [
542
+ /* @__PURE__ */ jsx("span", { className: "cgov-spinner", "aria-hidden": true }),
543
+ " Signing\u2026"
544
+ ] }) : "Sign & propose"
545
+ }
546
+ )
547
+ ] })
548
+ ] })
549
+ }
550
+ ) }) : null
551
+ ] });
552
+ }
553
+ var STATUS_LABEL = {
554
+ PROPOSED: "Needs approvals",
555
+ APPROVED: "Ready to execute",
556
+ EXECUTED: "Executed",
557
+ CANCELED: "Canceled",
558
+ EXPIRED: "Expired"
559
+ };
560
+ function proposalTitle(p) {
561
+ return p.title?.trim() || "Treasury action";
562
+ }
563
+ function ProposalRow(props) {
564
+ const p = props.proposal;
565
+ const signed = props.myOwnerIndex != null && p.signatures.some((s) => s.ownerId === props.myOwnerIndex);
566
+ const canApprove = p.status === "PROPOSED" && props.myOwnerIndex != null && !signed;
567
+ const canExecute = p.status === "APPROVED";
568
+ const label = props.agentLabel ?? "Agent";
569
+ const title = props.isAgent ? `${label} wants: ${proposalTitle(p)}` : proposalTitle(p);
570
+ return /* @__PURE__ */ jsxs("div", { className: "cgov-row", children: [
571
+ /* @__PURE__ */ jsxs("div", { className: "cgov-row-main", children: [
572
+ /* @__PURE__ */ jsx("p", { className: "cgov-row-title", children: title }),
573
+ /* @__PURE__ */ jsxs("div", { className: "cgov-row-sub", children: [
574
+ props.isAgent ? /* @__PURE__ */ jsx("span", { className: "cgov-chip cgov-chip-accent", children: label }) : null,
575
+ /* @__PURE__ */ jsx(
576
+ "span",
577
+ {
578
+ className: `cgov-chip${p.status === "EXECUTED" ? " cgov-chip-success" : p.status === "APPROVED" ? " cgov-chip-accent" : ""}`,
579
+ children: STATUS_LABEL[p.status]
580
+ }
581
+ ),
582
+ /* @__PURE__ */ jsxs("span", { children: [
583
+ p.signatures.length,
584
+ " of ",
585
+ p.requiredApprovals,
586
+ " approvals"
587
+ ] }),
588
+ p.risk === "SENSITIVE" ? /* @__PURE__ */ jsx("span", { className: "cgov-chip", children: "Sensitive" }) : null,
589
+ signed && p.status === "PROPOSED" ? /* @__PURE__ */ jsx("span", { children: "\xB7 you approved" }) : null,
590
+ p.status === "EXECUTED" && p.executedTxHash && props.explorerTxUrl ? /* @__PURE__ */ jsx(
591
+ "a",
592
+ {
593
+ href: props.explorerTxUrl(p.executedTxHash),
594
+ target: "_blank",
595
+ rel: "noopener noreferrer",
596
+ children: "receipt \u2197"
597
+ }
598
+ ) : null,
599
+ props.renderContext ? /* @__PURE__ */ jsx("span", { children: props.renderContext(p) }) : null
600
+ ] })
601
+ ] }),
602
+ canApprove ? /* @__PURE__ */ jsx(
603
+ "button",
604
+ {
605
+ type: "button",
606
+ className: "cgov-btn cgov-btn-primary cgov-btn-sm",
607
+ disabled: props.busy,
608
+ onClick: () => props.onApprove(p),
609
+ children: "Approve"
610
+ }
611
+ ) : canExecute ? /* @__PURE__ */ jsx(
612
+ "button",
613
+ {
614
+ type: "button",
615
+ className: "cgov-btn cgov-btn-primary cgov-btn-sm",
616
+ disabled: props.busy,
617
+ onClick: () => props.onExecute(p),
618
+ children: "Execute"
619
+ }
620
+ ) : null
621
+ ] });
622
+ }
623
+ function Approvals(props) {
624
+ const filter = props.filter ?? "actionable";
625
+ const [myOwnerIndex, setMyOwnerIndex] = useState(null);
626
+ const [actionError, setActionError] = useState(null);
627
+ useEffect(injectGovStyles, []);
628
+ useEffect(() => {
629
+ let on = true;
630
+ props.signer.resolveOwnerIndex(props.walletAddress).then(
631
+ (i) => on && setMyOwnerIndex(i),
632
+ () => on && setMyOwnerIndex(null)
633
+ // not an owner on this device → read-only
634
+ );
635
+ return () => {
636
+ on = false;
637
+ };
638
+ }, [props.walletAddress]);
639
+ const { proposals, approve, execute } = useTreasuryProposals({
640
+ walletAddress: props.walletAddress,
641
+ transport: props.transport,
642
+ signer: props.signer
643
+ });
644
+ const busy = approve.isPending || execute.isPending;
645
+ const onApprove = async (p) => {
646
+ setActionError(null);
647
+ try {
648
+ await approve.mutateAsync(p);
649
+ } catch (e) {
650
+ setActionError(e instanceof Error ? e.message : "Could not approve. Try again.");
651
+ }
652
+ };
653
+ const onExecute = async (p) => {
654
+ setActionError(null);
655
+ try {
656
+ const res = await execute.mutateAsync(p);
657
+ props.onExecuted?.(p, res.executedTxHash ?? res.transactionHash);
658
+ } catch (e) {
659
+ setActionError(e instanceof Error ? e.message : "Could not execute. Try again.");
660
+ }
661
+ };
662
+ const items = (proposals.data?.items ?? []).filter((p) => {
663
+ if (filter === "all") return true;
664
+ if (filter === "pending") return p.status === "PROPOSED" || p.status === "APPROVED";
665
+ return p.status === "APPROVED" || p.status === "PROPOSED" && (myOwnerIndex == null || !p.signatures.some((s) => s.ownerId === myOwnerIndex));
666
+ });
667
+ return /* @__PURE__ */ jsxs("div", { className: "cgov-root", style: themeVars(props.theme), children: [
668
+ /* @__PURE__ */ jsx("div", { className: "cgov-card", children: proposals.isLoading ? /* @__PURE__ */ jsxs("div", { className: "cgov-empty", children: [
669
+ /* @__PURE__ */ jsx("span", { className: "cgov-spinner", "aria-hidden": true }),
670
+ " Loading\u2026"
671
+ ] }) : proposals.error ? /* @__PURE__ */ jsx("div", { className: "cgov-empty", children: "Could not load proposals." }) : items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cgov-empty", children: props.empty ?? "You're all caught up." }) : items.map((p) => /* @__PURE__ */ jsx(
672
+ ProposalRow,
673
+ {
674
+ proposal: p,
675
+ myOwnerIndex,
676
+ busy,
677
+ onApprove,
678
+ onExecute,
679
+ renderContext: props.renderContext,
680
+ explorerTxUrl: props.explorerTxUrl,
681
+ isAgent: props.isAgentProposal?.(p) ?? false,
682
+ agentLabel: props.agentLabel
683
+ },
684
+ p.id
685
+ )) }),
686
+ actionError ? /* @__PURE__ */ jsx("div", { className: "cgov-error", role: "alert", children: actionError }) : null
687
+ ] });
688
+ }
689
+ function ApprovalInbox(props) {
690
+ useEffect(injectGovStyles, []);
691
+ return /* @__PURE__ */ jsx("div", { className: "cgov-root", style: themeVars(props.theme), children: props.treasuries.map((t) => /* @__PURE__ */ jsxs("section", { "aria-label": t.label, children: [
692
+ /* @__PURE__ */ jsx("p", { className: "cgov-group-label", children: t.label }),
693
+ /* @__PURE__ */ jsx(
694
+ Approvals,
695
+ {
696
+ walletAddress: t.walletAddress,
697
+ transport: t.transport,
698
+ signer: t.signer,
699
+ renderContext: t.renderContext,
700
+ explorerTxUrl: t.explorerTxUrl,
701
+ isAgentProposal: t.isAgentProposal,
702
+ agentLabel: t.agentLabel,
703
+ filter: "actionable",
704
+ theme: props.theme,
705
+ empty: props.onEmpty ?? "All caught up.",
706
+ onExecuted: (p, tx) => props.onExecuted?.(t, p, tx)
707
+ }
708
+ )
709
+ ] }, t.walletAddress)) });
710
+ }
711
+
712
+ export { ApprovalInbox, Approvals, DEFAULT_THEME, FELT_PRIME, FieldInput, OE_WINDOW_SECONDS, ProposeAction, SN_MAIN_CHAIN_ID, amountToBase, assembleActionExecuteCalldata, buildActionProposal, buildApproval, buildExecuteCalldata, createProposal, defineActions, deriveExecuteAfter, deserializeEnvelope, fieldSyntaxError, injectGovStyles, newOeNonce, proposalTitle, serializeEnvelope, signActionApproval, themeVars, toFelt, u256, useProposeAction, useTreasuryProposals, voteTemplate };
713
+ //# sourceMappingURL=multisig.mjs.map
714
+ //# sourceMappingURL=multisig.mjs.map