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