@umbra-privacy/ceremony 0.2.8 → 0.2.10
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/index.js +1371 -321
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,23 +4,38 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/components/App.tsx
|
|
7
|
-
import { useEffect as
|
|
8
|
-
import { Box as
|
|
7
|
+
import { useEffect as useEffect6, useState as useState6 } from "react";
|
|
8
|
+
import { Box as Box8, Text as Text8, useApp, useInput as useInput3 } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/cleanup.ts
|
|
11
11
|
var pendingLeaveQueue = null;
|
|
12
|
+
var pendingClearSession = null;
|
|
12
13
|
function setQueueCleanup(fn) {
|
|
13
14
|
pendingLeaveQueue = fn;
|
|
14
15
|
}
|
|
15
16
|
function clearQueueCleanup() {
|
|
16
17
|
pendingLeaveQueue = null;
|
|
17
18
|
}
|
|
19
|
+
function setSessionCleanup(fn) {
|
|
20
|
+
pendingClearSession = fn;
|
|
21
|
+
}
|
|
22
|
+
function clearSessionCleanup() {
|
|
23
|
+
pendingClearSession = null;
|
|
24
|
+
}
|
|
18
25
|
async function runQueueCleanup() {
|
|
19
26
|
if (!pendingLeaveQueue) return;
|
|
20
27
|
pendingLeaveQueue();
|
|
21
28
|
pendingLeaveQueue = null;
|
|
22
29
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
23
30
|
}
|
|
31
|
+
async function runSessionCleanup() {
|
|
32
|
+
if (!pendingClearSession) return;
|
|
33
|
+
try {
|
|
34
|
+
await pendingClearSession();
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
pendingClearSession = null;
|
|
38
|
+
}
|
|
24
39
|
|
|
25
40
|
// src/session.ts
|
|
26
41
|
import { readFile as readFile2, unlink as unlink2, writeFile } from "fs/promises";
|
|
@@ -227,7 +242,16 @@ var STORE_FILE = process.env["CEREMONY_CONTRIBUTIONS_FILE"] ?? join2(homedir2(),
|
|
|
227
242
|
async function load() {
|
|
228
243
|
try {
|
|
229
244
|
const raw = await readFile3(STORE_FILE, "utf8");
|
|
230
|
-
|
|
245
|
+
const parsed = JSON.parse(raw);
|
|
246
|
+
const out = {};
|
|
247
|
+
for (const [trackId, value] of Object.entries(parsed)) {
|
|
248
|
+
if (Array.isArray(value)) {
|
|
249
|
+
out[trackId] = value;
|
|
250
|
+
} else if (value && typeof value === "object") {
|
|
251
|
+
out[trackId] = [value];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
231
255
|
} catch {
|
|
232
256
|
return {};
|
|
233
257
|
}
|
|
@@ -240,9 +264,21 @@ async function getContributions() {
|
|
|
240
264
|
}
|
|
241
265
|
async function recordContribution(trackId, contribution) {
|
|
242
266
|
const store = await load();
|
|
243
|
-
store[trackId]
|
|
267
|
+
const existing = store[trackId] ?? [];
|
|
268
|
+
if (existing.some((c) => c.contributionId === contribution.contributionId)) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
store[trackId] = [...existing, contribution];
|
|
244
272
|
await save(store);
|
|
245
273
|
}
|
|
274
|
+
function latestContribution(store, trackId) {
|
|
275
|
+
const list = store[trackId];
|
|
276
|
+
if (!list || list.length === 0) return null;
|
|
277
|
+
return list.reduce((a, b) => b.sequenceNumber > a.sequenceNumber ? b : a);
|
|
278
|
+
}
|
|
279
|
+
function roundCount(store, trackId) {
|
|
280
|
+
return store[trackId]?.length ?? 0;
|
|
281
|
+
}
|
|
246
282
|
|
|
247
283
|
// src/components/Header.tsx
|
|
248
284
|
import { Box, Text } from "ink";
|
|
@@ -277,18 +313,27 @@ function trackStatusLabel(status) {
|
|
|
277
313
|
}
|
|
278
314
|
}
|
|
279
315
|
|
|
316
|
+
// src/theme.ts
|
|
317
|
+
var COLORS = {
|
|
318
|
+
/** Primary Umbra brand colour — #00B3FF. */
|
|
319
|
+
BRAND: "#00B3FF",
|
|
320
|
+
SUCCESS: "greenBright",
|
|
321
|
+
EMPHASIS: "cyanBright",
|
|
322
|
+
MUTED: "cyan"
|
|
323
|
+
};
|
|
324
|
+
|
|
280
325
|
// src/components/Header.tsx
|
|
281
326
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
282
327
|
var STATUS_COLOR = {
|
|
283
328
|
open: "green",
|
|
284
329
|
initialized: "yellow",
|
|
285
330
|
finalizing: "yellow",
|
|
286
|
-
completed:
|
|
331
|
+
completed: COLORS.BRAND
|
|
287
332
|
};
|
|
288
333
|
function Header({ ceremony, subtitle }) {
|
|
289
334
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
290
335
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
291
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color:
|
|
336
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: COLORS.BRAND, children: "\u25C6 Umbra Ceremony" }),
|
|
292
337
|
ceremony && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
293
338
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
|
|
294
339
|
/* @__PURE__ */ jsx(Text, { bold: true, children: ceremony.name }),
|
|
@@ -297,7 +342,7 @@ function Header({ ceremony, subtitle }) {
|
|
|
297
342
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "]" })
|
|
298
343
|
] })
|
|
299
344
|
] }),
|
|
300
|
-
/* @__PURE__ */ jsx(Text, { color:
|
|
345
|
+
/* @__PURE__ */ jsx(Text, { color: COLORS.BRAND, children: "Phase 2 trusted-setup contribution \xB7 Groth16 / BN254" }),
|
|
301
346
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "You are adding fresh entropy to Umbra's circuit proving keys. The on-chain privacy of every deposit, claim and transfer holds as long as ONE contributor in this ceremony destroys their secret." }),
|
|
302
347
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press ? at any time for a full explanation of what is happening." }),
|
|
303
348
|
ceremony && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
@@ -498,37 +543,46 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
|
|
|
498
543
|
minute: "2-digit"
|
|
499
544
|
}) : null;
|
|
500
545
|
const fastPoll = status.queue_position <= 2;
|
|
546
|
+
const aheadCount = Math.max(0, status.queue_position - 1);
|
|
547
|
+
const peopleWord = aheadCount === 1 ? "person" : "people";
|
|
501
548
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
|
|
502
549
|
/* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
|
|
503
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
504
|
-
"
|
|
550
|
+
aheadCount === 0 ? /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
551
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "green", children: "You're next" }),
|
|
552
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " in line" })
|
|
553
|
+
] }) : /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
554
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: aheadCount }),
|
|
505
555
|
" ",
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
556
|
+
peopleWord,
|
|
557
|
+
" ahead of you"
|
|
558
|
+
] }),
|
|
559
|
+
status.queue_depth > 1 && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
560
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
|
|
561
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
562
|
+
status.queue_depth,
|
|
563
|
+
" in queue"
|
|
510
564
|
] })
|
|
511
565
|
] }),
|
|
512
566
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
|
|
513
567
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
514
568
|
"Estimated wait:",
|
|
515
569
|
" ",
|
|
516
|
-
/* @__PURE__ */
|
|
517
|
-
"~",
|
|
518
|
-
waitMins,
|
|
519
|
-
" min"
|
|
520
|
-
] })
|
|
570
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: aheadCount === 0 ? "any moment now" : `~${waitMins} min` })
|
|
521
571
|
] })
|
|
522
572
|
] }),
|
|
523
|
-
status.status === "exporting" || status.status === "your_turn" || status.status === "ready_to_download" ? /* @__PURE__ */ jsx2(ExportingMessage, { status: status.status }) : status.active_since ? /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
573
|
+
status.status === "exporting" || status.status === "your_turn" || status.status === "ready_to_download" ? /* @__PURE__ */ jsx2(ExportingMessage, { status: status.status, slotExpiresAt: expiresAt }) : status.active_since ? /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
524
574
|
"Another contributor is active",
|
|
525
575
|
expiresAt ? ` \xB7 slot expires at ${expiresAt}` : ""
|
|
526
|
-
] }) }) :
|
|
576
|
+
] }) }) : aheadCount > 0 ? (
|
|
527
577
|
// Slot is idle but people are ahead — they joined and left without releasing.
|
|
528
578
|
// timeout_watchdog will clear each stale slot after contribution_timeout_secs.
|
|
529
|
-
/* @__PURE__ */
|
|
579
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
580
|
+
"Slot is idle \u2014 waiting for ",
|
|
581
|
+
peopleWord,
|
|
582
|
+
" ahead to respond or time out (up to ~5 min each)"
|
|
583
|
+
] })
|
|
530
584
|
) : (
|
|
531
|
-
//
|
|
585
|
+
// At the front of the line, slot idle — advance_queue should fire shortly.
|
|
532
586
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Slot is idle \u2014 your turn is being prepared..." })
|
|
533
587
|
),
|
|
534
588
|
pollErr ? /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
@@ -569,25 +623,38 @@ function TerminalSlotMessage({ status }) {
|
|
|
569
623
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: msg.detail })
|
|
570
624
|
] });
|
|
571
625
|
}
|
|
572
|
-
function ExportingMessage({
|
|
626
|
+
function ExportingMessage({
|
|
627
|
+
status,
|
|
628
|
+
slotExpiresAt
|
|
629
|
+
}) {
|
|
573
630
|
const exportingMsg = useCyclingMessage(EXPORTING_MESSAGES, 2500, status === "exporting");
|
|
574
|
-
return /* @__PURE__ */
|
|
575
|
-
/* @__PURE__ */ jsxs2(
|
|
576
|
-
/* @__PURE__ */
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
631
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
632
|
+
status === "exporting" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
633
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
|
|
634
|
+
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
|
|
635
|
+
" ",
|
|
636
|
+
exportingMsg,
|
|
637
|
+
"\u2026"
|
|
638
|
+
] }),
|
|
639
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Your slot is active. The worker is preparing the challenge file just for you." })
|
|
640
|
+
] }) : /* @__PURE__ */ jsx2(Text2, { color: "green", children: "Challenge ready \u2014 loading contribution flow\u2026" }),
|
|
641
|
+
slotExpiresAt && // Visible countdown is critical on power-18 circuits where the
|
|
642
|
+
// contributor compute window can run several minutes — without
|
|
643
|
+
// this they have no way to tell if they're about to be reaped.
|
|
644
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
645
|
+
"Your slot expires at ",
|
|
646
|
+
slotExpiresAt,
|
|
647
|
+
" (server time)."
|
|
648
|
+
] })
|
|
649
|
+
] });
|
|
583
650
|
}
|
|
584
651
|
|
|
585
|
-
// src/components/
|
|
586
|
-
import { useRef as useRef2, useState as useState3 } from "react";
|
|
652
|
+
// src/components/MouseEntropyCollector.tsx
|
|
653
|
+
import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
587
654
|
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
588
655
|
|
|
589
656
|
// src/entropy.ts
|
|
590
|
-
import { createHash as createHash2, randomBytes } from "crypto";
|
|
657
|
+
import { createHash as createHash2, hkdfSync, randomBytes } from "crypto";
|
|
591
658
|
function sha512(buf) {
|
|
592
659
|
return createHash2("sha512").update(buf).digest();
|
|
593
660
|
}
|
|
@@ -599,18 +666,405 @@ function buildEntropyFromKeystrokes(chars, timingsNs) {
|
|
|
599
666
|
const osHash = sha512(randomBytes(64));
|
|
600
667
|
return createHash2("sha512").update(keystrokeHash).update(osHash).digest("hex");
|
|
601
668
|
}
|
|
669
|
+
var TRAIL_MIN_BYTES = 64;
|
|
670
|
+
function buildEntropyFromMouseTrail(strokes) {
|
|
671
|
+
const strokeBuf = Buffer.alloc(strokes.length * 12);
|
|
672
|
+
for (let i = 0; i < strokes.length; i++) {
|
|
673
|
+
const s = strokes[i];
|
|
674
|
+
strokeBuf.writeUInt16BE(s.x & 65535, i * 12);
|
|
675
|
+
strokeBuf.writeUInt16BE(s.y & 65535, i * 12 + 2);
|
|
676
|
+
strokeBuf.writeBigUInt64BE(s.dtNs, i * 12 + 4);
|
|
677
|
+
}
|
|
678
|
+
const padBytes = Math.max(0, TRAIL_MIN_BYTES - strokeBuf.length);
|
|
679
|
+
const trailInput = padBytes > 0 ? Buffer.concat([strokeBuf, randomBytes(padBytes)]) : strokeBuf;
|
|
680
|
+
const trailHash = sha512(trailInput);
|
|
681
|
+
const osHash = sha512(randomBytes(64));
|
|
682
|
+
return createHash2("sha512").update(trailHash).update(osHash).digest("hex");
|
|
683
|
+
}
|
|
684
|
+
function deriveCircuitEntropy(masterSeed, ceremonyId2, circuitName) {
|
|
685
|
+
const ikm = Buffer.from(masterSeed, "hex");
|
|
686
|
+
const salt = createHash2("sha256").update(ceremonyId2).digest();
|
|
687
|
+
const info = Buffer.from(`umbra-ceremony-${ceremonyId2}-circuit-${circuitName}`, "utf8");
|
|
688
|
+
const derived = Buffer.from(hkdfSync("sha512", ikm, salt, info, 64));
|
|
689
|
+
return derived.toString("hex");
|
|
690
|
+
}
|
|
602
691
|
|
|
603
|
-
// src/
|
|
692
|
+
// src/mouse.ts
|
|
693
|
+
var SGR_RE = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
694
|
+
function classifyButton(buttonCode) {
|
|
695
|
+
if (buttonCode & 32) return "motion";
|
|
696
|
+
const base = buttonCode & 3;
|
|
697
|
+
if (base === 0) return "left";
|
|
698
|
+
if (base === 1) return "middle";
|
|
699
|
+
if (base === 2) return "right";
|
|
700
|
+
return "other";
|
|
701
|
+
}
|
|
702
|
+
function parseMouseChunk(chunk) {
|
|
703
|
+
const s = typeof chunk === "string" ? chunk : chunk.toString("binary");
|
|
704
|
+
const events = [];
|
|
705
|
+
SGR_RE.lastIndex = 0;
|
|
706
|
+
let m;
|
|
707
|
+
while ((m = SGR_RE.exec(s)) !== null) {
|
|
708
|
+
const buttonCode = parseInt(m[1], 10);
|
|
709
|
+
const col = parseInt(m[2], 10) - 1;
|
|
710
|
+
const row = parseInt(m[3], 10) - 1;
|
|
711
|
+
events.push({
|
|
712
|
+
button: classifyButton(buttonCode),
|
|
713
|
+
col,
|
|
714
|
+
row,
|
|
715
|
+
isRelease: m[4] === "m"
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return events;
|
|
719
|
+
}
|
|
720
|
+
function enableMouseReporting() {
|
|
721
|
+
process.stdout.write("\x1B[?1003h\x1B[?1006h");
|
|
722
|
+
return () => {
|
|
723
|
+
process.stdout.write("\x1B[?1003l\x1B[?1006l");
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/components/MouseEntropyCollector.tsx
|
|
604
728
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
729
|
+
var LEFT_OFFSET = 1;
|
|
730
|
+
var TOP_OFFSET = 2;
|
|
731
|
+
var MIN_W = 18;
|
|
732
|
+
var MIN_H = 12;
|
|
733
|
+
var MAX_W = 80;
|
|
734
|
+
var MAX_H = 32;
|
|
735
|
+
var DEFAULT_ASPECT = 2.2;
|
|
736
|
+
var ASPECT = (() => {
|
|
737
|
+
const raw = process.env["CEREMONY_CANVAS_ASPECT"];
|
|
738
|
+
if (!raw) return DEFAULT_ASPECT;
|
|
739
|
+
const parsed = Number.parseFloat(raw);
|
|
740
|
+
if (!Number.isFinite(parsed) || parsed < 0.5 || parsed > 6) return DEFAULT_ASPECT;
|
|
741
|
+
return parsed;
|
|
742
|
+
})();
|
|
743
|
+
var CHROME_ROWS = 6;
|
|
744
|
+
var CHROME_COLS = 2;
|
|
745
|
+
var MIN_ELAPSED_MS = 3e3;
|
|
746
|
+
var RENDER_INTERVAL_MS = 33;
|
|
747
|
+
function deriveCanvasSize(cols, rows) {
|
|
748
|
+
const availH = rows - CHROME_ROWS;
|
|
749
|
+
const availW = cols - CHROME_COLS;
|
|
750
|
+
let h = Math.min(MAX_H, availH);
|
|
751
|
+
let w = Math.round(h * ASPECT);
|
|
752
|
+
const wCap = Math.min(MAX_W, availW);
|
|
753
|
+
if (w > wCap) {
|
|
754
|
+
w = wCap;
|
|
755
|
+
h = Math.round(w / ASPECT);
|
|
756
|
+
}
|
|
757
|
+
if (w < MIN_W || h < MIN_H) return null;
|
|
758
|
+
return { w, h };
|
|
759
|
+
}
|
|
760
|
+
function readTerminalDims() {
|
|
761
|
+
return {
|
|
762
|
+
cols: process.stdout.columns ?? 80,
|
|
763
|
+
rows: process.stdout.rows ?? 24
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function MouseEntropyCollector({ title, onComplete, onError }) {
|
|
767
|
+
const [dims, setDims] = useState3(() => {
|
|
768
|
+
const { cols, rows } = readTerminalDims();
|
|
769
|
+
return deriveCanvasSize(cols, rows);
|
|
770
|
+
});
|
|
771
|
+
const dimsRef = useRef2(dims);
|
|
772
|
+
useEffect3(() => {
|
|
773
|
+
dimsRef.current = dims;
|
|
774
|
+
}, [dims]);
|
|
775
|
+
const pixelsRef = useRef2(/* @__PURE__ */ new Set());
|
|
776
|
+
const strokesRef = useRef2([]);
|
|
777
|
+
const mouseDownRef = useRef2(false);
|
|
778
|
+
const lastPaintRef = useRef2(null);
|
|
779
|
+
const cursorRef = useRef2(null);
|
|
780
|
+
const lastStrokeTsRef = useRef2(process.hrtime.bigint());
|
|
781
|
+
const startTsRef = useRef2(process.hrtime.bigint());
|
|
782
|
+
const completedRef = useRef2(false);
|
|
783
|
+
const [pixelCount, setPixelCount] = useState3(0);
|
|
784
|
+
const [elapsedMs, setElapsedMs] = useState3(0);
|
|
785
|
+
const [done, setDone] = useState3(false);
|
|
786
|
+
const [nudge, setNudge] = useState3(null);
|
|
787
|
+
const [grid, setGrid] = useState3(
|
|
788
|
+
() => dims ? Array.from({ length: dims.h }, () => " ".repeat(dims.w)) : []
|
|
789
|
+
);
|
|
790
|
+
const [cursor, setCursor] = useState3(null);
|
|
791
|
+
const canvasW = dims?.w ?? 0;
|
|
792
|
+
const canvasH = dims?.h ?? 0;
|
|
793
|
+
const subH = canvasH * 2;
|
|
794
|
+
function idx(x, y) {
|
|
795
|
+
return y * canvasW + x;
|
|
796
|
+
}
|
|
797
|
+
function paintLine(pixels, x0, y0, x1, y1, onNew) {
|
|
798
|
+
let dx = Math.abs(x1 - x0);
|
|
799
|
+
let dy = -Math.abs(y1 - y0);
|
|
800
|
+
const sx = x0 < x1 ? 1 : -1;
|
|
801
|
+
const sy = y0 < y1 ? 1 : -1;
|
|
802
|
+
let err = dx + dy;
|
|
803
|
+
let x = x0;
|
|
804
|
+
let y = y0;
|
|
805
|
+
while (true) {
|
|
806
|
+
if (x >= 0 && x < canvasW && y >= 0 && y < subH) {
|
|
807
|
+
const id = idx(x, y);
|
|
808
|
+
if (!pixels.has(id)) {
|
|
809
|
+
pixels.add(id);
|
|
810
|
+
onNew(x, y);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
if (x === x1 && y === y1) break;
|
|
814
|
+
const e2 = 2 * err;
|
|
815
|
+
if (e2 >= dy) {
|
|
816
|
+
if (x === x1) break;
|
|
817
|
+
err += dy;
|
|
818
|
+
x += sx;
|
|
819
|
+
}
|
|
820
|
+
if (e2 <= dx) {
|
|
821
|
+
if (y === y1) break;
|
|
822
|
+
err += dx;
|
|
823
|
+
y += sy;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
useEffect3(() => {
|
|
828
|
+
const onResize = () => {
|
|
829
|
+
const { cols, rows } = readTerminalDims();
|
|
830
|
+
const next = deriveCanvasSize(cols, rows);
|
|
831
|
+
const cur = dimsRef.current;
|
|
832
|
+
if (next === null && cur === null) return;
|
|
833
|
+
if (next !== null && cur !== null && next.w === cur.w && next.h === cur.h) return;
|
|
834
|
+
pixelsRef.current.clear();
|
|
835
|
+
strokesRef.current = [];
|
|
836
|
+
lastPaintRef.current = null;
|
|
837
|
+
cursorRef.current = null;
|
|
838
|
+
startTsRef.current = process.hrtime.bigint();
|
|
839
|
+
lastStrokeTsRef.current = startTsRef.current;
|
|
840
|
+
setNudge(null);
|
|
841
|
+
setPixelCount(0);
|
|
842
|
+
setElapsedMs(0);
|
|
843
|
+
setCursor(null);
|
|
844
|
+
setGrid(next ? Array.from({ length: next.h }, () => " ".repeat(next.w)) : []);
|
|
845
|
+
setDims(next);
|
|
846
|
+
};
|
|
847
|
+
process.stdout.on("resize", onResize);
|
|
848
|
+
return () => {
|
|
849
|
+
process.stdout.off("resize", onResize);
|
|
850
|
+
};
|
|
851
|
+
}, []);
|
|
852
|
+
useEffect3(() => {
|
|
853
|
+
if (!dims) return;
|
|
854
|
+
const disableMouse = enableMouseReporting();
|
|
855
|
+
const onData = (chunk) => {
|
|
856
|
+
if (completedRef.current) return;
|
|
857
|
+
const events = parseMouseChunk(chunk);
|
|
858
|
+
for (const ev of events) handleMouseEvent(ev);
|
|
859
|
+
};
|
|
860
|
+
process.stdin.on("data", onData);
|
|
861
|
+
const renderTick = setInterval(() => {
|
|
862
|
+
if (completedRef.current) return;
|
|
863
|
+
const size = pixelsRef.current.size;
|
|
864
|
+
const elapsed = Number((process.hrtime.bigint() - startTsRef.current) / 1000000n);
|
|
865
|
+
if (size !== pixelCount) setPixelCount(size);
|
|
866
|
+
if (elapsed !== elapsedMs) setElapsedMs(elapsed);
|
|
867
|
+
const next = [];
|
|
868
|
+
for (let r = 0; r < canvasH; r++) {
|
|
869
|
+
let line = "";
|
|
870
|
+
for (let c = 0; c < canvasW; c++) {
|
|
871
|
+
const top = pixelsRef.current.has(idx(c, r * 2));
|
|
872
|
+
const bot = pixelsRef.current.has(idx(c, r * 2 + 1));
|
|
873
|
+
line += top && bot ? "\u2588" : top ? "\u2580" : bot ? "\u2584" : " ";
|
|
874
|
+
}
|
|
875
|
+
next.push(line);
|
|
876
|
+
}
|
|
877
|
+
setGrid(next);
|
|
878
|
+
setCursor(cursorRef.current);
|
|
879
|
+
}, RENDER_INTERVAL_MS);
|
|
880
|
+
return () => {
|
|
881
|
+
clearInterval(renderTick);
|
|
882
|
+
process.stdin.off("data", onData);
|
|
883
|
+
disableMouse();
|
|
884
|
+
};
|
|
885
|
+
}, [canvasW, canvasH]);
|
|
886
|
+
function handleMouseEvent(ev) {
|
|
887
|
+
if (completedRef.current) return;
|
|
888
|
+
const canvasCol = ev.col - LEFT_OFFSET;
|
|
889
|
+
const canvasRow = ev.row - TOP_OFFSET;
|
|
890
|
+
const inBounds = canvasCol >= 0 && canvasCol < canvasW && canvasRow >= 0 && canvasRow < canvasH;
|
|
891
|
+
if (inBounds) cursorRef.current = { row: canvasRow, col: canvasCol };
|
|
892
|
+
else cursorRef.current = null;
|
|
893
|
+
if (ev.button === "left" && !ev.isRelease) {
|
|
894
|
+
mouseDownRef.current = true;
|
|
895
|
+
lastPaintRef.current = null;
|
|
896
|
+
if (inBounds) paintAt(canvasCol, canvasRow);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (ev.isRelease) {
|
|
900
|
+
mouseDownRef.current = false;
|
|
901
|
+
lastPaintRef.current = null;
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (ev.button === "motion" && mouseDownRef.current && inBounds) {
|
|
905
|
+
paintAt(canvasCol, canvasRow);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function paintAt(canvasCol, canvasRow) {
|
|
909
|
+
const x = canvasCol;
|
|
910
|
+
const yTop = canvasRow * 2;
|
|
911
|
+
const yBot = canvasRow * 2 + 1;
|
|
912
|
+
const now = process.hrtime.bigint();
|
|
913
|
+
const dt = now - lastStrokeTsRef.current;
|
|
914
|
+
lastStrokeTsRef.current = now;
|
|
915
|
+
if (lastPaintRef.current) {
|
|
916
|
+
const lp = lastPaintRef.current;
|
|
917
|
+
paintLine(pixelsRef.current, lp.x, lp.y, x, yTop, (px, py) => {
|
|
918
|
+
strokesRef.current.push({ x: px, y: py, dtNs: dt });
|
|
919
|
+
});
|
|
920
|
+
paintLine(pixelsRef.current, lp.x, lp.y + 1, x, yBot, () => {
|
|
921
|
+
});
|
|
922
|
+
} else {
|
|
923
|
+
if (!pixelsRef.current.has(idx(x, yTop))) {
|
|
924
|
+
pixelsRef.current.add(idx(x, yTop));
|
|
925
|
+
strokesRef.current.push({ x, y: yTop, dtNs: dt });
|
|
926
|
+
}
|
|
927
|
+
if (!pixelsRef.current.has(idx(x, yBot))) {
|
|
928
|
+
pixelsRef.current.add(idx(x, yBot));
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
lastPaintRef.current = { x, y: yTop };
|
|
932
|
+
}
|
|
933
|
+
useInput((input, key) => {
|
|
934
|
+
if (completedRef.current) return;
|
|
935
|
+
if (key.backspace || key.delete || input === "c" || input === "C") {
|
|
936
|
+
pixelsRef.current.clear();
|
|
937
|
+
strokesRef.current = [];
|
|
938
|
+
lastPaintRef.current = null;
|
|
939
|
+
cursorRef.current = null;
|
|
940
|
+
startTsRef.current = process.hrtime.bigint();
|
|
941
|
+
lastStrokeTsRef.current = startTsRef.current;
|
|
942
|
+
setNudge(null);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (key.return) {
|
|
946
|
+
const elapsed = Number((process.hrtime.bigint() - startTsRef.current) / 1000000n);
|
|
947
|
+
if (elapsed < MIN_ELAPSED_MS) {
|
|
948
|
+
const secLeft = Math.ceil((MIN_ELAPSED_MS - elapsed) / 1e3);
|
|
949
|
+
setNudge(`Wait ${secLeft}s before committing (anti-fat-finger).`);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
completedRef.current = true;
|
|
953
|
+
setDone(true);
|
|
954
|
+
try {
|
|
955
|
+
const entropy = buildEntropyFromMouseTrail(strokesRef.current);
|
|
956
|
+
setTimeout(() => onComplete(entropy), 250);
|
|
957
|
+
} catch (e) {
|
|
958
|
+
onError(e instanceof Error ? e : new Error(String(e)));
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
if (!dims) {
|
|
963
|
+
const { cols, rows } = readTerminalDims();
|
|
964
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
|
|
965
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: "\u26A0 Terminal too small for the mouse canvas" }),
|
|
966
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
967
|
+
"Need at least ",
|
|
968
|
+
MIN_W + CHROME_COLS,
|
|
969
|
+
" cols \xD7 ",
|
|
970
|
+
MIN_H + CHROME_ROWS,
|
|
971
|
+
" rows. Got ",
|
|
972
|
+
cols,
|
|
973
|
+
" \xD7 ",
|
|
974
|
+
rows,
|
|
975
|
+
"."
|
|
976
|
+
] }),
|
|
977
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
978
|
+
"Resize this window larger, or press ",
|
|
979
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "\u232B Backspace" }),
|
|
980
|
+
" to go back and then ",
|
|
981
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "magenta", children: "K" }),
|
|
982
|
+
" on the mode-select modal to use keyboard entropy instead."
|
|
983
|
+
] })
|
|
984
|
+
] });
|
|
985
|
+
}
|
|
986
|
+
const secsElapsed = Math.floor(elapsedMs / 1e3);
|
|
987
|
+
const minSecs = Math.ceil(MIN_ELAPSED_MS / 1e3);
|
|
988
|
+
const timePct = Math.min(100, Math.round(elapsedMs / MIN_ELAPSED_MS * 100));
|
|
989
|
+
const filled = Math.round(timePct / 100 * 20);
|
|
990
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
|
|
991
|
+
const ready = elapsedMs >= MIN_ELAPSED_MS;
|
|
992
|
+
if (done) {
|
|
993
|
+
return /* @__PURE__ */ jsxs3(Box3, { gap: 2, children: [
|
|
994
|
+
/* @__PURE__ */ jsx3(Text3, { color: "green", bold: true, children: "\u2713" }),
|
|
995
|
+
/* @__PURE__ */ jsx3(Text3, { children: "Entropy committed." })
|
|
996
|
+
] });
|
|
997
|
+
}
|
|
998
|
+
const renderRow = (rowStr, rowIdx) => {
|
|
999
|
+
if (!cursor || cursor.row !== rowIdx) {
|
|
1000
|
+
return /* @__PURE__ */ jsx3(Text3, { color: "white", children: rowStr });
|
|
1001
|
+
}
|
|
1002
|
+
const cIdx = cursor.col;
|
|
1003
|
+
const before = rowStr.slice(0, cIdx);
|
|
1004
|
+
const at = rowStr[cIdx] ?? " ";
|
|
1005
|
+
const after = rowStr.slice(cIdx + 1);
|
|
1006
|
+
return /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1007
|
+
/* @__PURE__ */ jsx3(Text3, { color: "white", children: before }),
|
|
1008
|
+
/* @__PURE__ */ jsx3(Text3, { color: "yellow", inverse: true, children: at === " " ? "\xB7" : at }),
|
|
1009
|
+
/* @__PURE__ */ jsx3(Text3, { color: "white", children: after })
|
|
1010
|
+
] });
|
|
1011
|
+
};
|
|
1012
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx3(Text3, { children: title ? /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: title.length > 70 ? title.slice(0, 67) + "\u2026" : title }) : /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Draw to generate entropy \u2014 hold the LEFT mouse button and drag" }) }),
|
|
1014
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1015
|
+
"\u250C",
|
|
1016
|
+
"\u2500".repeat(canvasW),
|
|
1017
|
+
"\u2510"
|
|
1018
|
+
] }),
|
|
1019
|
+
grid.map((row, i) => /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1020
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2502" }),
|
|
1021
|
+
renderRow(row, i),
|
|
1022
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2502" })
|
|
1023
|
+
] }, i)),
|
|
1024
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1025
|
+
"\u2514",
|
|
1026
|
+
"\u2500".repeat(canvasW),
|
|
1027
|
+
"\u2518"
|
|
1028
|
+
] }),
|
|
1029
|
+
/* @__PURE__ */ jsxs3(Box3, { gap: 2, children: [
|
|
1030
|
+
/* @__PURE__ */ jsxs3(Text3, { color: ready ? COLORS.SUCCESS : COLORS.BRAND, children: [
|
|
1031
|
+
"[",
|
|
1032
|
+
bar,
|
|
1033
|
+
"]"
|
|
1034
|
+
] }),
|
|
1035
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1036
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: ready ? "green" : "yellow", children: [
|
|
1037
|
+
secsElapsed,
|
|
1038
|
+
"s"
|
|
1039
|
+
] }),
|
|
1040
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1041
|
+
" ",
|
|
1042
|
+
"/ ",
|
|
1043
|
+
minSecs,
|
|
1044
|
+
"s \xB7 ",
|
|
1045
|
+
pixelCount,
|
|
1046
|
+
" pixels drawn"
|
|
1047
|
+
] })
|
|
1048
|
+
] }),
|
|
1049
|
+
nudge && /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: nudge })
|
|
1050
|
+
] }),
|
|
1051
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Enter = commit \xB7 c / \u232B = clear \xB7 Ctrl+C = abort" })
|
|
1052
|
+
] });
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/components/EntropyCollector.tsx
|
|
1056
|
+
import { useRef as useRef3, useState as useState4 } from "react";
|
|
1057
|
+
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
1058
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
605
1059
|
var TARGET = 20;
|
|
606
1060
|
function EntropyCollector({ onComplete, onError }) {
|
|
607
|
-
const [count, setCount] =
|
|
608
|
-
const [done, setDone] =
|
|
609
|
-
const charsRef =
|
|
610
|
-
const timingsRef =
|
|
611
|
-
const lastRef =
|
|
612
|
-
const completedRef =
|
|
613
|
-
|
|
1061
|
+
const [count, setCount] = useState4(0);
|
|
1062
|
+
const [done, setDone] = useState4(false);
|
|
1063
|
+
const charsRef = useRef3([]);
|
|
1064
|
+
const timingsRef = useRef3([]);
|
|
1065
|
+
const lastRef = useRef3(process.hrtime.bigint());
|
|
1066
|
+
const completedRef = useRef3(false);
|
|
1067
|
+
useInput2((input) => {
|
|
614
1068
|
if (completedRef.current) return;
|
|
615
1069
|
const now = process.hrtime.bigint();
|
|
616
1070
|
timingsRef.current.push(now - lastRef.current);
|
|
@@ -634,32 +1088,32 @@ function EntropyCollector({ onComplete, onError }) {
|
|
|
634
1088
|
const pct = Math.round(count / TARGET * 100);
|
|
635
1089
|
const stars = "*".repeat(Math.min(count, 32));
|
|
636
1090
|
if (done) {
|
|
637
|
-
return /* @__PURE__ */
|
|
638
|
-
/* @__PURE__ */
|
|
639
|
-
/* @__PURE__ */
|
|
1091
|
+
return /* @__PURE__ */ jsxs4(Box4, { gap: 2, children: [
|
|
1092
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", bold: true, children: "\u2713" }),
|
|
1093
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
640
1094
|
"Entropy collected \u2014 [",
|
|
641
1095
|
bar,
|
|
642
1096
|
"] 100%"
|
|
643
1097
|
] })
|
|
644
1098
|
] });
|
|
645
1099
|
}
|
|
646
|
-
return /* @__PURE__ */
|
|
647
|
-
/* @__PURE__ */
|
|
648
|
-
/* @__PURE__ */
|
|
649
|
-
/* @__PURE__ */
|
|
650
|
-
/* @__PURE__ */
|
|
651
|
-
/* @__PURE__ */
|
|
652
|
-
/* @__PURE__ */
|
|
1100
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
|
|
1101
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Type anything to generate entropy:" }),
|
|
1102
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Keystroke timing (nanosecond intervals) is the randomness source." }),
|
|
1103
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
1104
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " > " }),
|
|
1105
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: stars }),
|
|
1106
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "\u2588" })
|
|
653
1107
|
] }),
|
|
654
|
-
/* @__PURE__ */
|
|
655
|
-
/* @__PURE__ */
|
|
1108
|
+
/* @__PURE__ */ jsxs4(Box4, { gap: 2, marginTop: 1, children: [
|
|
1109
|
+
/* @__PURE__ */ jsxs4(Text4, { color: COLORS.BRAND, children: [
|
|
656
1110
|
"[",
|
|
657
1111
|
bar,
|
|
658
1112
|
"]"
|
|
659
1113
|
] }),
|
|
660
|
-
/* @__PURE__ */
|
|
661
|
-
/* @__PURE__ */
|
|
662
|
-
/* @__PURE__ */
|
|
1114
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1115
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: count > 0 ? "green" : "yellow", children: count }),
|
|
1116
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
663
1117
|
"/",
|
|
664
1118
|
TARGET,
|
|
665
1119
|
" keystrokes (",
|
|
@@ -668,13 +1122,14 @@ function EntropyCollector({ onComplete, onError }) {
|
|
|
668
1122
|
] })
|
|
669
1123
|
] })
|
|
670
1124
|
] }),
|
|
671
|
-
/* @__PURE__ */
|
|
1125
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Shown as * \u2014 your actual input is never revealed." }),
|
|
1126
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "To quit without contributing: Ctrl+C (Q is part of the entropy alphabet here)." })
|
|
672
1127
|
] });
|
|
673
1128
|
}
|
|
674
1129
|
|
|
675
1130
|
// src/components/ContributeFlow.tsx
|
|
676
|
-
import { useEffect as
|
|
677
|
-
import { Box as
|
|
1131
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
1132
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
678
1133
|
import Spinner2 from "ink-spinner";
|
|
679
1134
|
import { tmpdir as tmpdir2 } from "os";
|
|
680
1135
|
import { join as join4 } from "path";
|
|
@@ -701,7 +1156,7 @@ async function cleanupTemp(path) {
|
|
|
701
1156
|
}
|
|
702
1157
|
|
|
703
1158
|
// src/components/ContributeFlow.tsx
|
|
704
|
-
import { jsx as
|
|
1159
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
705
1160
|
var STEP_LABELS = ["downloading", "computing", "uploading", "signalling", "verifying"];
|
|
706
1161
|
var STEP_INDEX = {
|
|
707
1162
|
downloading: 0,
|
|
@@ -712,8 +1167,8 @@ var STEP_INDEX = {
|
|
|
712
1167
|
};
|
|
713
1168
|
function ContributeFlow(props) {
|
|
714
1169
|
const { ceremonyId: ceremonyId2, trackId, token, slotStatus, entropy, displayName: displayName2 } = props;
|
|
715
|
-
const [step, setStep] =
|
|
716
|
-
|
|
1170
|
+
const [step, setStep] = useState5({ name: "downloading", bytesReceived: 0, total: null });
|
|
1171
|
+
useEffect5(() => {
|
|
717
1172
|
let cancelled = false;
|
|
718
1173
|
const challengePath = join4(tmpdir2(), `ceremony-challenge-${Date.now()}.mpcparams`);
|
|
719
1174
|
let responsePath = null;
|
|
@@ -796,112 +1251,144 @@ function ContributeFlow(props) {
|
|
|
796
1251
|
const currentIdx = STEP_INDEX[step.name] ?? 0;
|
|
797
1252
|
const computingMsg = useCyclingMessage(COMPUTING_MESSAGES, 2500, step.name === "computing");
|
|
798
1253
|
const verifyingMsg = useCyclingMessage(VERIFYING_MESSAGES, 3e3, step.name === "verifying");
|
|
799
|
-
return /* @__PURE__ */
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
/* @__PURE__ */
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1254
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
|
|
1255
|
+
STEP_LABELS.map((label, i) => {
|
|
1256
|
+
const isDone = i < currentIdx;
|
|
1257
|
+
const isActive = i === currentIdx;
|
|
1258
|
+
const isPending = i > currentIdx;
|
|
1259
|
+
let indicator;
|
|
1260
|
+
if (isDone) indicator = /* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u2713" });
|
|
1261
|
+
else if (isActive) indicator = /* @__PURE__ */ jsx5(Spinner2, { type: "dots" });
|
|
1262
|
+
else indicator = /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u25CB" });
|
|
1263
|
+
let detail = null;
|
|
1264
|
+
if (isActive && step.name === "downloading" && step.total) {
|
|
1265
|
+
const pct = Math.round(step.bytesReceived / step.total * 100);
|
|
1266
|
+
const filled = Math.round(pct / 5);
|
|
1267
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
|
|
1268
|
+
detail = /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1269
|
+
" ",
|
|
1270
|
+
"[",
|
|
1271
|
+
bar,
|
|
1272
|
+
"] ",
|
|
1273
|
+
pct,
|
|
1274
|
+
"%"
|
|
1275
|
+
] });
|
|
1276
|
+
}
|
|
1277
|
+
if (isActive && step.name === "verifying") {
|
|
1278
|
+
const elapsed = step.attempt <= 10 ? step.attempt * 3 : 10 * 3 + (step.attempt - 10) * 10;
|
|
1279
|
+
const mins = Math.floor(elapsed / 60);
|
|
1280
|
+
const secs = elapsed % 60;
|
|
1281
|
+
const time = mins > 0 ? `${mins}m ${secs.toString().padStart(2, "0")}s` : `${secs}s`;
|
|
1282
|
+
detail = /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1283
|
+
" ",
|
|
1284
|
+
verifyingMsg,
|
|
1285
|
+
"\u2026 (",
|
|
1286
|
+
time,
|
|
1287
|
+
" elapsed)"
|
|
1288
|
+
] });
|
|
1289
|
+
}
|
|
1290
|
+
if (isActive && step.name === "computing") {
|
|
1291
|
+
detail = /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1292
|
+
" ",
|
|
1293
|
+
computingMsg,
|
|
1294
|
+
"\u2026"
|
|
1295
|
+
] });
|
|
1296
|
+
}
|
|
1297
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1298
|
+
/* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
|
|
1299
|
+
indicator,
|
|
1300
|
+
/* @__PURE__ */ jsxs5(
|
|
1301
|
+
Text5,
|
|
1302
|
+
{
|
|
1303
|
+
color: isDone ? "green" : isActive ? "white" : void 0,
|
|
1304
|
+
bold: isActive,
|
|
1305
|
+
dimColor: isPending,
|
|
1306
|
+
children: [
|
|
1307
|
+
label.charAt(0).toUpperCase() + label.slice(1),
|
|
1308
|
+
" ",
|
|
1309
|
+
isActive && label === "computing" && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "(entropy stays local)" })
|
|
1310
|
+
]
|
|
1311
|
+
}
|
|
1312
|
+
)
|
|
1313
|
+
] }),
|
|
1314
|
+
detail
|
|
1315
|
+
] }, label);
|
|
1316
|
+
}),
|
|
1317
|
+
slotStatus.slot_expires_at && step.name !== "verifying" && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1318
|
+
"Your slot expires at",
|
|
1319
|
+
" ",
|
|
1320
|
+
new Date(slotStatus.slot_expires_at).toLocaleTimeString([], {
|
|
1321
|
+
hour: "2-digit",
|
|
1322
|
+
minute: "2-digit"
|
|
1323
|
+
}),
|
|
1324
|
+
" ",
|
|
1325
|
+
"(server time)."
|
|
1326
|
+
] }) }),
|
|
1327
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
1328
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
1329
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1330
|
+
"Need to bail? Press ",
|
|
1331
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Q" }),
|
|
1332
|
+
" or ",
|
|
1333
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Ctrl+C" }),
|
|
1334
|
+
" \u2014 your slot is released immediately so the next contributor can take over."
|
|
857
1335
|
] }),
|
|
858
|
-
|
|
859
|
-
] }
|
|
860
|
-
|
|
1336
|
+
step.name === "computing" && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "(During this step snarkjs is busy \u2014 Ctrl+C responds faster than Q.)" })
|
|
1337
|
+
] })
|
|
1338
|
+
] });
|
|
861
1339
|
}
|
|
862
1340
|
|
|
863
1341
|
// src/components/Attestation.tsx
|
|
864
|
-
import { Box as
|
|
865
|
-
import { jsx as
|
|
1342
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1343
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
866
1344
|
function Attestation({ contribution }) {
|
|
867
1345
|
const hashShort = contribution.contributionHash ? `${contribution.contributionHash.slice(0, 16)}...${contribution.contributionHash.slice(-8)}` : "(pending verification)";
|
|
868
1346
|
const verifiedAt = contribution.verifiedAt ? new Date(contribution.verifiedAt).toLocaleString() : null;
|
|
869
|
-
return /* @__PURE__ */
|
|
870
|
-
/* @__PURE__ */
|
|
871
|
-
/* @__PURE__ */
|
|
872
|
-
/* @__PURE__ */
|
|
873
|
-
/* @__PURE__ */
|
|
874
|
-
/* @__PURE__ */
|
|
1347
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", gap: 1, children: [
|
|
1348
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "greenBright", children: "\u2714 Contribution verified!" }),
|
|
1349
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", gap: 0, children: [
|
|
1350
|
+
/* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
1351
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, color: "cyan", children: "Circuit" }),
|
|
1352
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "blueBright", children: contribution.circuitName })
|
|
875
1353
|
] }),
|
|
876
|
-
/* @__PURE__ */
|
|
877
|
-
/* @__PURE__ */
|
|
878
|
-
|
|
1354
|
+
/* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
1355
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, color: "cyan", children: [
|
|
1356
|
+
"Round",
|
|
1357
|
+
" "
|
|
1358
|
+
] }),
|
|
1359
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: "greenBright", children: [
|
|
879
1360
|
"#",
|
|
880
1361
|
contribution.sequenceNumber
|
|
881
1362
|
] })
|
|
882
1363
|
] }),
|
|
883
|
-
/* @__PURE__ */
|
|
884
|
-
/* @__PURE__ */
|
|
885
|
-
|
|
1364
|
+
/* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
1365
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, color: "cyan", children: [
|
|
1366
|
+
"Hash",
|
|
1367
|
+
" "
|
|
1368
|
+
] }),
|
|
1369
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyanBright", children: hashShort })
|
|
886
1370
|
] }),
|
|
887
|
-
verifiedAt && /* @__PURE__ */
|
|
888
|
-
/* @__PURE__ */
|
|
889
|
-
|
|
1371
|
+
verifiedAt && /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
1372
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, color: "cyan", children: [
|
|
1373
|
+
"Time",
|
|
1374
|
+
" "
|
|
1375
|
+
] }),
|
|
1376
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: verifiedAt })
|
|
890
1377
|
] })
|
|
891
1378
|
] }),
|
|
892
|
-
contribution.contributionHash && /* @__PURE__ */
|
|
893
|
-
/* @__PURE__ */
|
|
894
|
-
/* @__PURE__ */
|
|
1379
|
+
contribution.contributionHash && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
|
|
1380
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Full hash (share this to prove participation):" }),
|
|
1381
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyanBright", wrap: "wrap", children: contribution.contributionHash })
|
|
895
1382
|
] })
|
|
896
1383
|
] });
|
|
897
1384
|
}
|
|
898
1385
|
|
|
899
1386
|
// src/components/InfoModal.tsx
|
|
900
|
-
import { Box as
|
|
901
|
-
import { jsx as
|
|
1387
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1388
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
902
1389
|
function InfoModal() {
|
|
903
|
-
return /* @__PURE__ */
|
|
904
|
-
|
|
1390
|
+
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsxs7(
|
|
1391
|
+
Box7,
|
|
905
1392
|
{
|
|
906
1393
|
flexDirection: "column",
|
|
907
1394
|
borderStyle: "round",
|
|
@@ -911,17 +1398,33 @@ function InfoModal() {
|
|
|
911
1398
|
width: 84,
|
|
912
1399
|
gap: 1,
|
|
913
1400
|
children: [
|
|
914
|
-
/* @__PURE__ */
|
|
915
|
-
/* @__PURE__ */
|
|
916
|
-
/* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
1401
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "Umbra Phase 2 Trusted-Setup Ceremony" }),
|
|
1402
|
+
/* @__PURE__ */ jsx7(Text7, { children: "Umbra uses Groth16 zero-knowledge proofs to give Solana users on-chain privacy. Every circuit needs a one-time multi-party ceremony to generate its proving key safely \u2014 that is Phase 2. Each contributor adds their own secret entropy and destroys it afterward. The setup stays secure as long as AT LEAST ONE contributor erased theirs, which is why your single contribution genuinely matters." }),
|
|
1403
|
+
/* @__PURE__ */ jsx7(Text7, { children: "What you will do: tap 20 keys to seed your entropy, download the latest challenge file, run snarkjs locally to combine your secret with the parameters, and upload the response. Your secret never leaves your machine. Erase it when you are done." }),
|
|
1404
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Press B, Esc or ? to close" })
|
|
918
1405
|
]
|
|
919
1406
|
}
|
|
920
1407
|
) });
|
|
921
1408
|
}
|
|
922
1409
|
|
|
1410
|
+
// src/exit-hints.ts
|
|
1411
|
+
var QUIT_HINTS = {
|
|
1412
|
+
/** Idle screens with no active slot — ceremony picker, tracks list, etc. */
|
|
1413
|
+
quit: "Q / Ctrl+C to quit",
|
|
1414
|
+
/** Auto-mode driving the loop — emphasises "abort run", not just "quit". */
|
|
1415
|
+
abortRun: "Q / Ctrl+C abort run",
|
|
1416
|
+
/** Between-circuits auto-mode "queueing" — calls out the slot-release side effect. */
|
|
1417
|
+
abortSlot: "Q / Ctrl+C to abort (releases any active slot)"
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// src/slot-release.ts
|
|
1421
|
+
function fireAndForgetLeaveQueue(ceremonyId2, trackId, sessionToken) {
|
|
1422
|
+
api.leaveQueue(ceremonyId2, trackId, sessionToken).catch(() => {
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
|
|
923
1426
|
// src/components/App.tsx
|
|
924
|
-
import { jsx as
|
|
1427
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
925
1428
|
var NAME_MAX_LEN = 100;
|
|
926
1429
|
var NAME_VALID_RE = /^[\p{L}\p{N} _.\-]*$/u;
|
|
927
1430
|
function elideMiddle(s, maxLen) {
|
|
@@ -930,27 +1433,47 @@ function elideMiddle(s, maxLen) {
|
|
|
930
1433
|
const keepTail = Math.floor((maxLen - 1) / 2);
|
|
931
1434
|
return s.slice(0, keepHead) + "\u2026" + s.slice(s.length - keepTail);
|
|
932
1435
|
}
|
|
1436
|
+
function orderTracksForAuto(tracks, contributed) {
|
|
1437
|
+
const openTracks = tracks.filter((t) => t.status === "open");
|
|
1438
|
+
const fresh = [];
|
|
1439
|
+
const alreadyDone = [];
|
|
1440
|
+
for (const t of openTracks) {
|
|
1441
|
+
if ((contributed[t.id]?.length ?? 0) > 0) alreadyDone.push(t);
|
|
1442
|
+
else fresh.push(t);
|
|
1443
|
+
}
|
|
1444
|
+
return [...fresh, ...alreadyDone];
|
|
1445
|
+
}
|
|
933
1446
|
function copyToClipboardOSC52(value) {
|
|
934
1447
|
const payload = Buffer.from(value, "utf8").toString("base64");
|
|
935
1448
|
process.stdout.write(`\x1B]52;c;${payload}\x07`);
|
|
936
1449
|
}
|
|
937
1450
|
function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName }) {
|
|
938
1451
|
const { exit } = useApp();
|
|
939
|
-
const [activeCeremonyId, setActiveCeremonyId] =
|
|
940
|
-
const [displayName2, setDisplayName] =
|
|
941
|
-
const [nameSet, setNameSet] =
|
|
942
|
-
const [screen,
|
|
1452
|
+
const [activeCeremonyId, setActiveCeremonyId] = useState6(initialCeremonyId);
|
|
1453
|
+
const [displayName2, setDisplayName] = useState6(initialDisplayName ?? "anonymous");
|
|
1454
|
+
const [nameSet, setNameSet] = useState6(initialDisplayName !== void 0);
|
|
1455
|
+
const [screen, setScreenRaw] = useState6(
|
|
943
1456
|
initialDisplayName === void 0 ? { name: "name-input", value: "" } : initialCeremonyId ? { name: "loading" } : { name: "ceremony-picker", ceremonies: [], loading: true }
|
|
944
1457
|
);
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1458
|
+
const setScreen = (next) => {
|
|
1459
|
+
setScreenRaw((prev) => {
|
|
1460
|
+
const resolved = typeof next === "function" ? next(prev) : next;
|
|
1461
|
+
if (prev.name !== resolved.name) {
|
|
1462
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
1463
|
+
}
|
|
1464
|
+
return resolved;
|
|
1465
|
+
});
|
|
1466
|
+
};
|
|
1467
|
+
const [ceremony, setCeremony] = useState6(null);
|
|
1468
|
+
const [session, setSession] = useState6(null);
|
|
1469
|
+
const [contributed, setContributed] = useState6({});
|
|
1470
|
+
const [selectedIdx, setSelectedIdx] = useState6(0);
|
|
1471
|
+
const [tab, setTab] = useState6(0);
|
|
1472
|
+
const [contribCursor, setContribCursor] = useState6(0);
|
|
1473
|
+
const [copyToast, setCopyToast] = useState6(null);
|
|
1474
|
+
const [showInfo, setShowInfo] = useState6(false);
|
|
1475
|
+
const [entropyMode, setEntropyMode] = useState6("mouse");
|
|
1476
|
+
useEffect6(() => {
|
|
954
1477
|
if (!nameSet) return;
|
|
955
1478
|
if (!initialCeremonyId) {
|
|
956
1479
|
loadCeremonies();
|
|
@@ -998,7 +1521,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
998
1521
|
return;
|
|
999
1522
|
}
|
|
1000
1523
|
setSelectedIdx(0);
|
|
1001
|
-
setScreen({ name: "
|
|
1524
|
+
setScreen({ name: "mode-select", tracks });
|
|
1002
1525
|
} catch (e) {
|
|
1003
1526
|
if (e.code === "INVALID_SESSION" || e.status === 401) {
|
|
1004
1527
|
try {
|
|
@@ -1015,7 +1538,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1015
1538
|
return;
|
|
1016
1539
|
}
|
|
1017
1540
|
setSelectedIdx(0);
|
|
1018
|
-
setScreen({ name: "
|
|
1541
|
+
setScreen({ name: "mode-select", tracks });
|
|
1019
1542
|
} catch (e2) {
|
|
1020
1543
|
setScreen({
|
|
1021
1544
|
name: "error",
|
|
@@ -1073,36 +1596,112 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1073
1596
|
setScreen({ name: "ceremony-picker", ceremonies: [], loading: true });
|
|
1074
1597
|
loadCeremonies();
|
|
1075
1598
|
}
|
|
1076
|
-
|
|
1077
|
-
if (
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1599
|
+
useEffect6(() => {
|
|
1600
|
+
if (session) {
|
|
1601
|
+
setSessionCleanup(clearSession);
|
|
1602
|
+
} else {
|
|
1603
|
+
clearSessionCleanup();
|
|
1604
|
+
}
|
|
1605
|
+
}, [session]);
|
|
1606
|
+
useEffect6(() => {
|
|
1607
|
+
let trackId = null;
|
|
1608
|
+
if (screen.name === "queue" || screen.name === "entropy" || screen.name === "contribute") {
|
|
1609
|
+
trackId = screen.trackId;
|
|
1610
|
+
} else if (screen.name === "auto-running") {
|
|
1611
|
+
if (screen.subPhase.kind === "waiting" || screen.subPhase.kind === "contributing" || screen.subPhase.kind === "releasing") {
|
|
1612
|
+
trackId = screen.subPhase.trackId;
|
|
1613
|
+
} else if (screen.current) {
|
|
1614
|
+
trackId = screen.current.id;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (trackId && session) {
|
|
1618
|
+
const sessionToken = session.session_token;
|
|
1619
|
+
setQueueCleanup(() => fireAndForgetLeaveQueue(activeCeremonyId, trackId, sessionToken));
|
|
1083
1620
|
} else {
|
|
1084
1621
|
clearQueueCleanup();
|
|
1085
1622
|
}
|
|
1086
|
-
}, [screen.name]);
|
|
1087
|
-
|
|
1623
|
+
}, [screen.name, screen.name === "auto-running" ? screen.subPhase.kind : null]);
|
|
1624
|
+
useEffect6(() => {
|
|
1625
|
+
if (screen.name !== "auto-running") return;
|
|
1626
|
+
if (screen.current !== null) return;
|
|
1627
|
+
if (screen.subPhase.kind !== "queueing") return;
|
|
1628
|
+
if (!session) return;
|
|
1629
|
+
const currentScreen = screen;
|
|
1630
|
+
(async () => {
|
|
1631
|
+
let next = null;
|
|
1632
|
+
let nextRemaining = currentScreen.remaining;
|
|
1633
|
+
let nextRetryQueue = currentScreen.retryQueue;
|
|
1634
|
+
let nextRetryingPhase = currentScreen.retryingPhase;
|
|
1635
|
+
if (currentScreen.remaining.length > 0) {
|
|
1636
|
+
next = currentScreen.remaining[0];
|
|
1637
|
+
nextRemaining = currentScreen.remaining.slice(1);
|
|
1638
|
+
} else if (currentScreen.retryQueue.length > 0 && !currentScreen.retryingPhase) {
|
|
1639
|
+
nextRemaining = currentScreen.retryQueue.slice(1);
|
|
1640
|
+
next = currentScreen.retryQueue[0];
|
|
1641
|
+
nextRetryQueue = [];
|
|
1642
|
+
nextRetryingPhase = true;
|
|
1643
|
+
}
|
|
1644
|
+
if (!next) {
|
|
1645
|
+
setScreen({ name: "auto-summary", outcomes: currentScreen.done });
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
try {
|
|
1649
|
+
await api.joinQueue(activeCeremonyId, next.id, session.session_token);
|
|
1650
|
+
} catch (e) {
|
|
1651
|
+
const reason = e?.message ?? String(e);
|
|
1652
|
+
setScreen({
|
|
1653
|
+
...currentScreen,
|
|
1654
|
+
remaining: nextRemaining,
|
|
1655
|
+
retryQueue: nextRetryQueue,
|
|
1656
|
+
retryingPhase: nextRetryingPhase,
|
|
1657
|
+
current: null,
|
|
1658
|
+
subPhase: { kind: "queueing" },
|
|
1659
|
+
done: [
|
|
1660
|
+
...currentScreen.done,
|
|
1661
|
+
{
|
|
1662
|
+
trackId: next.id,
|
|
1663
|
+
circuitName: next.circuit_name,
|
|
1664
|
+
result: "failed",
|
|
1665
|
+
reason: `joinQueue: ${reason}`
|
|
1666
|
+
}
|
|
1667
|
+
]
|
|
1668
|
+
});
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
setScreen({
|
|
1672
|
+
...currentScreen,
|
|
1673
|
+
remaining: nextRemaining,
|
|
1674
|
+
retryQueue: nextRetryQueue,
|
|
1675
|
+
retryingPhase: nextRetryingPhase,
|
|
1676
|
+
current: next,
|
|
1677
|
+
subPhase: { kind: "waiting", trackId: next.id, circuitName: next.circuit_name }
|
|
1678
|
+
});
|
|
1679
|
+
})();
|
|
1680
|
+
}, [
|
|
1681
|
+
screen.name,
|
|
1682
|
+
screen.name === "auto-running" ? screen.subPhase.kind : null,
|
|
1683
|
+
screen.name === "auto-running" ? screen.current?.id ?? null : null
|
|
1684
|
+
]);
|
|
1685
|
+
useInput3((input, key) => {
|
|
1088
1686
|
const q = input.toLowerCase();
|
|
1687
|
+
const isQuit = q === "q" || key.ctrl === true && q === "c";
|
|
1089
1688
|
if (showInfo) {
|
|
1090
1689
|
if (key.escape || input === "?" || q === "b") {
|
|
1091
1690
|
setShowInfo(false);
|
|
1092
1691
|
return;
|
|
1093
1692
|
}
|
|
1094
|
-
if (
|
|
1693
|
+
if (isQuit) {
|
|
1095
1694
|
exit();
|
|
1096
1695
|
return;
|
|
1097
1696
|
}
|
|
1098
1697
|
return;
|
|
1099
1698
|
}
|
|
1100
|
-
if (input === "?" && screen.name !== "entropy") {
|
|
1699
|
+
if (input === "?" && screen.name !== "entropy" && screen.name !== "auto-entropy") {
|
|
1101
1700
|
setShowInfo(true);
|
|
1102
1701
|
return;
|
|
1103
1702
|
}
|
|
1104
1703
|
if (screen.name === "name-input") {
|
|
1105
|
-
if (key.
|
|
1704
|
+
if (key.ctrl === true && q === "c") {
|
|
1106
1705
|
exit();
|
|
1107
1706
|
return;
|
|
1108
1707
|
}
|
|
@@ -1118,29 +1717,89 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1118
1717
|
setScreen({ name: "name-input", value: screen.value.slice(0, -1) });
|
|
1119
1718
|
return;
|
|
1120
1719
|
}
|
|
1121
|
-
if (input && input.length === 1 && NAME_VALID_RE.test(input)) {
|
|
1720
|
+
if (input && input.length === 1 && !key.ctrl && !key.meta && NAME_VALID_RE.test(input)) {
|
|
1122
1721
|
if (screen.value.length >= NAME_MAX_LEN) return;
|
|
1123
1722
|
setScreen({ name: "name-input", value: screen.value + input });
|
|
1124
1723
|
}
|
|
1125
1724
|
return;
|
|
1126
1725
|
}
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
1726
|
+
if (isQuit && screen.name !== "entropy" && screen.name !== "auto-entropy") {
|
|
1727
|
+
let trackId = null;
|
|
1728
|
+
if (screen.name === "queue" || screen.name === "contribute") {
|
|
1729
|
+
trackId = screen.trackId;
|
|
1730
|
+
} else if (screen.name === "auto-running") {
|
|
1731
|
+
if (screen.subPhase.kind === "waiting" || screen.subPhase.kind === "contributing" || screen.subPhase.kind === "releasing") {
|
|
1732
|
+
trackId = screen.subPhase.trackId;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (trackId && session) {
|
|
1129
1736
|
clearQueueCleanup();
|
|
1130
|
-
|
|
1737
|
+
clearSessionCleanup();
|
|
1738
|
+
fireAndForgetLeaveQueue(activeCeremonyId, trackId, session.session_token);
|
|
1739
|
+
clearSession().catch(() => {
|
|
1131
1740
|
});
|
|
1132
1741
|
setTimeout(() => exit(), 500);
|
|
1742
|
+
} else if (session) {
|
|
1743
|
+
clearSessionCleanup();
|
|
1744
|
+
clearSession().catch(() => {
|
|
1745
|
+
});
|
|
1746
|
+
setTimeout(() => exit(), 100);
|
|
1133
1747
|
} else {
|
|
1134
1748
|
exit();
|
|
1135
1749
|
}
|
|
1136
1750
|
return;
|
|
1137
1751
|
}
|
|
1138
1752
|
if (key.backspace || key.delete) {
|
|
1139
|
-
if (!initialCeremonyId && (screen.name === "tracks" || screen.name === "error")) {
|
|
1753
|
+
if (!initialCeremonyId && (screen.name === "tracks" || screen.name === "error" || screen.name === "mode-select" || screen.name === "auto-summary")) {
|
|
1140
1754
|
goCeremonyPicker();
|
|
1141
1755
|
return;
|
|
1142
1756
|
}
|
|
1143
|
-
if (screen.name
|
|
1757
|
+
if (screen.name === "auto-running" && (screen.subPhase.kind === "waiting" || screen.subPhase.kind === "contributing") && screen.current && session) {
|
|
1758
|
+
const skipped = screen.current;
|
|
1759
|
+
const skippedTrackId = screen.subPhase.trackId;
|
|
1760
|
+
const skippedCircuitName = screen.subPhase.circuitName;
|
|
1761
|
+
const sessionToken = session.session_token;
|
|
1762
|
+
setScreen({
|
|
1763
|
+
...screen,
|
|
1764
|
+
subPhase: {
|
|
1765
|
+
kind: "releasing",
|
|
1766
|
+
trackId: skippedTrackId,
|
|
1767
|
+
circuitName: skippedCircuitName
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
api.leaveQueue(activeCeremonyId, skippedTrackId, sessionToken).catch((err) => {
|
|
1771
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1772
|
+
process.stderr.write(
|
|
1773
|
+
`[ceremony-tui] leaveQueue(${skippedTrackId}) failed during skip: ${msg}
|
|
1774
|
+
`
|
|
1775
|
+
);
|
|
1776
|
+
}).finally(() => {
|
|
1777
|
+
setScreen((prev) => {
|
|
1778
|
+
if (prev.name !== "auto-running" || prev.subPhase.kind !== "releasing" || prev.subPhase.trackId !== skippedTrackId) {
|
|
1779
|
+
return prev;
|
|
1780
|
+
}
|
|
1781
|
+
return {
|
|
1782
|
+
...prev,
|
|
1783
|
+
current: null,
|
|
1784
|
+
subPhase: { kind: "queueing" },
|
|
1785
|
+
done: [
|
|
1786
|
+
...prev.done,
|
|
1787
|
+
{
|
|
1788
|
+
trackId: skipped.id,
|
|
1789
|
+
circuitName: skipped.circuit_name,
|
|
1790
|
+
result: "failed",
|
|
1791
|
+
reason: "user skipped"
|
|
1792
|
+
}
|
|
1793
|
+
]
|
|
1794
|
+
// Don't queue skipped circuits for retry — the user
|
|
1795
|
+
// actively chose to skip, so a silent retry would
|
|
1796
|
+
// defeat that intent.
|
|
1797
|
+
};
|
|
1798
|
+
});
|
|
1799
|
+
});
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (screen.name !== "tracks" && screen.name !== "loading" && screen.name !== "joining" && screen.name !== "entropy" && screen.name !== "auto-entropy" && screen.name !== "ceremony-picker") {
|
|
1144
1803
|
goHome();
|
|
1145
1804
|
}
|
|
1146
1805
|
return;
|
|
@@ -1165,6 +1824,22 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1165
1824
|
}
|
|
1166
1825
|
return;
|
|
1167
1826
|
}
|
|
1827
|
+
if (screen.name === "mode-select") {
|
|
1828
|
+
if (q === "k") {
|
|
1829
|
+
setEntropyMode((m) => m === "mouse" ? "keyboard" : "mouse");
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (key.return) {
|
|
1833
|
+
setScreen({ name: "auto-entropy", tracks: screen.tracks });
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
if (q === "n" || q === "m") {
|
|
1837
|
+
setSelectedIdx(0);
|
|
1838
|
+
setScreen({ name: "tracks", tracks: screen.tracks });
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1168
1843
|
if (screen.name === "tracks") {
|
|
1169
1844
|
const { tracks } = screen;
|
|
1170
1845
|
if (key.tab) {
|
|
@@ -1183,13 +1858,15 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1183
1858
|
}
|
|
1184
1859
|
if (key.return) {
|
|
1185
1860
|
const t = tracks[selectedIdx];
|
|
1186
|
-
if (t && t.status === "open"
|
|
1861
|
+
if (t && t.status === "open") joinTrack(t);
|
|
1187
1862
|
return;
|
|
1188
1863
|
}
|
|
1189
1864
|
} else if (tab === 1) {
|
|
1190
|
-
const myContribs = Object.values(contributed).filter(
|
|
1191
|
-
|
|
1192
|
-
|
|
1865
|
+
const myContribs = Object.values(contributed).flat().filter((c) => c.ceremonyId === activeCeremonyId).sort((a, b) => {
|
|
1866
|
+
const ta = a.verifiedAt ? Date.parse(a.verifiedAt) : 0;
|
|
1867
|
+
const tb = b.verifiedAt ? Date.parse(b.verifiedAt) : 0;
|
|
1868
|
+
return tb - ta;
|
|
1869
|
+
});
|
|
1193
1870
|
if (key.upArrow) {
|
|
1194
1871
|
setContribCursor((i) => Math.max(0, i - 1));
|
|
1195
1872
|
return;
|
|
@@ -1221,52 +1898,55 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1221
1898
|
}
|
|
1222
1899
|
});
|
|
1223
1900
|
if (showInfo) {
|
|
1224
|
-
return /* @__PURE__ */
|
|
1901
|
+
return /* @__PURE__ */ jsx8(InfoModal, {});
|
|
1225
1902
|
}
|
|
1226
1903
|
if (screen.name === "name-input") {
|
|
1227
1904
|
const { value } = screen;
|
|
1228
1905
|
const canSubmit = value.trim().length > 0;
|
|
1229
|
-
return /* @__PURE__ */
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
/* @__PURE__ */
|
|
1234
|
-
/* @__PURE__ */
|
|
1235
|
-
/* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
/* @__PURE__ */
|
|
1906
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1907
|
+
/* @__PURE__ */ jsx8(Header, { ceremony: null }),
|
|
1908
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, gap: 1, children: [
|
|
1909
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Who are you contributing as?" }),
|
|
1910
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Your display name appears next to your contributions in the public transcript. Pick anything \u2014 your real name, a handle, or hit Tab for a random anonymous name." }),
|
|
1911
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
1912
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", children: " > " }),
|
|
1913
|
+
/* @__PURE__ */ jsx8(Text8, { children: value }),
|
|
1914
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", inverse: true, children: " " })
|
|
1238
1915
|
] }),
|
|
1239
|
-
/* @__PURE__ */
|
|
1916
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: canSubmit ? "Enter to continue \xB7 Tab rerolls random name \xB7 \u232B backspace \xB7 Ctrl+C to quit" : "Type a name, or press Tab to suggest a random anonymous one \xB7 Ctrl+C to quit" })
|
|
1240
1917
|
] })
|
|
1241
1918
|
] });
|
|
1242
1919
|
}
|
|
1243
1920
|
if (screen.name === "ceremony-picker") {
|
|
1244
1921
|
const { ceremonies, loading } = screen;
|
|
1245
|
-
return /* @__PURE__ */
|
|
1246
|
-
/* @__PURE__ */
|
|
1247
|
-
loading ? /* @__PURE__ */
|
|
1248
|
-
/* @__PURE__ */
|
|
1249
|
-
/* @__PURE__ */
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
/* @__PURE__ */
|
|
1253
|
-
/* @__PURE__ */
|
|
1922
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1923
|
+
/* @__PURE__ */ jsx8(Header, { ceremony: null }),
|
|
1924
|
+
loading ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1925
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Loading ceremonies..." }),
|
|
1926
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: QUIT_HINTS.quit }) })
|
|
1927
|
+
] }) : ceremonies.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
|
|
1928
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "No ceremonies found." }),
|
|
1929
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "The server may not have any active ceremonies yet." }),
|
|
1930
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: QUIT_HINTS.quit })
|
|
1931
|
+
] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1932
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Select a ceremony:" }),
|
|
1933
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " " + "\u2500".repeat(60) }),
|
|
1254
1934
|
ceremonies.map((c, i) => {
|
|
1255
1935
|
const isSelected = i === selectedIdx;
|
|
1256
1936
|
const isOpen = c.status === "open";
|
|
1257
1937
|
const statusColor = c.status === "open" ? "green" : c.status === "completed" ? "cyan" : "yellow";
|
|
1258
1938
|
const statusLabel = ceremonyStatusLabel(c.status);
|
|
1259
|
-
return /* @__PURE__ */
|
|
1260
|
-
/* @__PURE__ */
|
|
1939
|
+
return /* @__PURE__ */ jsxs8(Box8, { gap: 2, children: [
|
|
1940
|
+
/* @__PURE__ */ jsxs8(Text8, { color: isSelected ? "cyan" : isOpen ? void 0 : "gray", children: [
|
|
1261
1941
|
isSelected ? "\u25B6 " : " ",
|
|
1262
1942
|
c.name.padEnd(30)
|
|
1263
1943
|
] }),
|
|
1264
|
-
/* @__PURE__ */
|
|
1944
|
+
/* @__PURE__ */ jsxs8(Text8, { color: statusColor, children: [
|
|
1265
1945
|
"[",
|
|
1266
1946
|
statusLabel,
|
|
1267
1947
|
"]"
|
|
1268
1948
|
] }),
|
|
1269
|
-
/* @__PURE__ */
|
|
1949
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1270
1950
|
c.track_count,
|
|
1271
1951
|
" track",
|
|
1272
1952
|
c.track_count !== 1 ? "s" : "",
|
|
@@ -1277,65 +1957,391 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1277
1957
|
] })
|
|
1278
1958
|
] }, c.id);
|
|
1279
1959
|
}),
|
|
1280
|
-
/* @__PURE__ */
|
|
1960
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " " + "\u2500".repeat(60) }),
|
|
1281
1961
|
(() => {
|
|
1282
1962
|
const c = ceremonies[selectedIdx];
|
|
1283
1963
|
if (!c) return null;
|
|
1964
|
+
const navHint = /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1965
|
+
"\u2191/\u2193 select \xB7 Enter join \xB7 ",
|
|
1966
|
+
QUIT_HINTS.quit
|
|
1967
|
+
] }) });
|
|
1284
1968
|
if (c.status === "initialized") {
|
|
1285
|
-
return /* @__PURE__ */
|
|
1286
|
-
"
|
|
1287
|
-
|
|
1969
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1970
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
1971
|
+
" ",
|
|
1972
|
+
"This ceremony is not yet open for contributions. The Umbra team is still preparing the circuits \u2014 please check back shortly."
|
|
1973
|
+
] }),
|
|
1974
|
+
navHint
|
|
1288
1975
|
] });
|
|
1289
1976
|
}
|
|
1290
1977
|
if (c.status === "finalizing") {
|
|
1291
|
-
return /* @__PURE__ */
|
|
1292
|
-
"
|
|
1293
|
-
|
|
1978
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1979
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
1980
|
+
" ",
|
|
1981
|
+
"Contributions are closed. The ceremony is computing the final verification key \u2014 no further contributions can be added."
|
|
1982
|
+
] }),
|
|
1983
|
+
navHint
|
|
1294
1984
|
] });
|
|
1295
1985
|
}
|
|
1296
1986
|
if (c.status === "completed") {
|
|
1297
|
-
return /* @__PURE__ */
|
|
1298
|
-
|
|
1299
|
-
|
|
1987
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1988
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1989
|
+
" ",
|
|
1990
|
+
/* @__PURE__ */ jsx8(Text8, { color: "greenBright", bold: true, children: "\u2714 Ceremony complete." }),
|
|
1991
|
+
" ",
|
|
1992
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyanBright", children: "The verification keys are finalised" }),
|
|
1993
|
+
/* @__PURE__ */ jsx8(Text8, { color: "blueBright", children: " \u2014 thank you to everyone who contributed." })
|
|
1994
|
+
] }),
|
|
1995
|
+
navHint
|
|
1300
1996
|
] });
|
|
1301
1997
|
}
|
|
1302
|
-
return
|
|
1998
|
+
return navHint;
|
|
1303
1999
|
})()
|
|
1304
2000
|
] })
|
|
1305
2001
|
] });
|
|
1306
2002
|
}
|
|
1307
2003
|
if (screen.name === "loading") {
|
|
1308
|
-
return /* @__PURE__ */
|
|
1309
|
-
/* @__PURE__ */
|
|
1310
|
-
/* @__PURE__ */
|
|
2004
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2005
|
+
/* @__PURE__ */ jsx8(Header, { ceremony }),
|
|
2006
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Loading tracks..." }),
|
|
2007
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: QUIT_HINTS.quit }) })
|
|
2008
|
+
] });
|
|
2009
|
+
}
|
|
2010
|
+
if (screen.name === "mode-select") {
|
|
2011
|
+
const { tracks } = screen;
|
|
2012
|
+
const openCount = tracks.filter((t) => t.status === "open").length;
|
|
2013
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2014
|
+
/* @__PURE__ */ jsx8(Header, { ceremony }),
|
|
2015
|
+
/* @__PURE__ */ jsxs8(
|
|
2016
|
+
Box8,
|
|
2017
|
+
{
|
|
2018
|
+
flexDirection: "column",
|
|
2019
|
+
borderStyle: "round",
|
|
2020
|
+
borderColor: "cyan",
|
|
2021
|
+
paddingX: 2,
|
|
2022
|
+
paddingY: 0,
|
|
2023
|
+
children: [
|
|
2024
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: "Automate?" }),
|
|
2025
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2026
|
+
openCount,
|
|
2027
|
+
" circuit",
|
|
2028
|
+
openCount === 1 ? "" : "s",
|
|
2029
|
+
" open \xB7 auto runs the whole loop"
|
|
2030
|
+
] }),
|
|
2031
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2032
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2033
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "green", children: "Enter" }),
|
|
2034
|
+
" ",
|
|
2035
|
+
"auto \xB7 entropy once, every circuit in order"
|
|
2036
|
+
] }),
|
|
2037
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2038
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "yellow", children: "N" }),
|
|
2039
|
+
" ",
|
|
2040
|
+
"manual \xB7 pick circuits one at a time"
|
|
2041
|
+
] }),
|
|
2042
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2043
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "magenta", children: "K" }),
|
|
2044
|
+
" ",
|
|
2045
|
+
"input:",
|
|
2046
|
+
" ",
|
|
2047
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: entropyMode === "mouse" ? "cyan" : "yellow", children: entropyMode }),
|
|
2048
|
+
" ",
|
|
2049
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "(press K to toggle)" })
|
|
2050
|
+
] })
|
|
2051
|
+
] })
|
|
2052
|
+
]
|
|
2053
|
+
}
|
|
2054
|
+
),
|
|
2055
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2056
|
+
"\u232B back \xB7 ",
|
|
2057
|
+
QUIT_HINTS.quit
|
|
2058
|
+
] }) })
|
|
2059
|
+
] });
|
|
2060
|
+
}
|
|
2061
|
+
if (screen.name === "auto-entropy") {
|
|
2062
|
+
const { tracks } = screen;
|
|
2063
|
+
const onComplete = (masterSeed) => {
|
|
2064
|
+
const ordered = orderTracksForAuto(tracks, contributed);
|
|
2065
|
+
setScreen({
|
|
2066
|
+
name: "auto-running",
|
|
2067
|
+
tracks,
|
|
2068
|
+
masterSeed,
|
|
2069
|
+
remaining: ordered,
|
|
2070
|
+
retryQueue: [],
|
|
2071
|
+
retryingPhase: false,
|
|
2072
|
+
done: [],
|
|
2073
|
+
current: null,
|
|
2074
|
+
subPhase: { kind: "queueing" }
|
|
2075
|
+
});
|
|
2076
|
+
};
|
|
2077
|
+
const onError = (e) => setScreen({ name: "error", message: e.message, recoverable: false });
|
|
2078
|
+
if (entropyMode === "mouse") {
|
|
2079
|
+
return /* @__PURE__ */ jsx8(
|
|
2080
|
+
MouseEntropyCollector,
|
|
2081
|
+
{
|
|
2082
|
+
title: `Auto-mode entropy \xB7 one drawing seeds every circuit (HKDF per circuit)`,
|
|
2083
|
+
onComplete,
|
|
2084
|
+
onError
|
|
2085
|
+
}
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2089
|
+
/* @__PURE__ */ jsx8(Header, { ceremony, subtitle: "Auto-mode entropy \xB7 seeds every circuit (HKDF)" }),
|
|
2090
|
+
/* @__PURE__ */ jsx8(EntropyCollector, { onComplete, onError })
|
|
2091
|
+
] });
|
|
2092
|
+
}
|
|
2093
|
+
if (screen.name === "auto-running") {
|
|
2094
|
+
const totalCount = screen.done.length + screen.remaining.length + screen.retryQueue.length + (screen.current ? 1 : 0);
|
|
2095
|
+
const verifiedCount = screen.done.filter((d) => d.result === "verified").length;
|
|
2096
|
+
const failedCount = screen.done.filter((d) => d.result === "failed").length;
|
|
2097
|
+
const currentLabel = screen.current ? `current: ${screen.current.circuit_name}` : "advancing to next circuit\u2026";
|
|
2098
|
+
const progressStrip = /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
2099
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u2500".repeat(60) }),
|
|
2100
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2101
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: "Auto-mode" }),
|
|
2102
|
+
" \xB7 ",
|
|
2103
|
+
/* @__PURE__ */ jsx8(Text8, { children: verifiedCount }),
|
|
2104
|
+
"/",
|
|
2105
|
+
/* @__PURE__ */ jsx8(Text8, { children: totalCount }),
|
|
2106
|
+
" verified",
|
|
2107
|
+
failedCount > 0 && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2108
|
+
" \xB7 ",
|
|
2109
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
2110
|
+
failedCount,
|
|
2111
|
+
" failed"
|
|
2112
|
+
] }),
|
|
2113
|
+
screen.retryingPhase ? " (retrying)" : ""
|
|
2114
|
+
] }),
|
|
2115
|
+
screen.retryQueue.length > 0 && !screen.retryingPhase && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2116
|
+
" \xB7 ",
|
|
2117
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2118
|
+
screen.retryQueue.length,
|
|
2119
|
+
" queued for retry"
|
|
2120
|
+
] })
|
|
2121
|
+
] })
|
|
2122
|
+
] }),
|
|
2123
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: currentLabel }),
|
|
2124
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u2500".repeat(60) })
|
|
2125
|
+
] });
|
|
2126
|
+
if (screen.subPhase.kind === "releasing") {
|
|
2127
|
+
const sub = screen.subPhase;
|
|
2128
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2129
|
+
/* @__PURE__ */ jsx8(
|
|
2130
|
+
Header,
|
|
2131
|
+
{
|
|
2132
|
+
ceremony,
|
|
2133
|
+
subtitle: `Auto-mode \xB7 releasing slot for ${sub.circuitName}`
|
|
2134
|
+
}
|
|
2135
|
+
),
|
|
2136
|
+
progressStrip,
|
|
2137
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
2138
|
+
"\u23F3 Releasing slot for ",
|
|
2139
|
+
sub.circuitName,
|
|
2140
|
+
"\u2026"
|
|
2141
|
+
] }),
|
|
2142
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Waiting for the server to confirm the slot is free before joining the next circuit's queue." }),
|
|
2143
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: QUIT_HINTS.abortRun })
|
|
2144
|
+
] });
|
|
2145
|
+
}
|
|
2146
|
+
if (screen.subPhase.kind === "queueing") {
|
|
2147
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2148
|
+
/* @__PURE__ */ jsx8(Header, { ceremony, subtitle: "Auto-mode \xB7 joining next queue" }),
|
|
2149
|
+
progressStrip,
|
|
2150
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Picking the next circuit\u2026" }),
|
|
2151
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: QUIT_HINTS.abortSlot })
|
|
2152
|
+
] });
|
|
2153
|
+
}
|
|
2154
|
+
if (screen.subPhase.kind === "waiting") {
|
|
2155
|
+
const sub = screen.subPhase;
|
|
2156
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2157
|
+
/* @__PURE__ */ jsx8(
|
|
2158
|
+
Header,
|
|
2159
|
+
{
|
|
2160
|
+
ceremony,
|
|
2161
|
+
subtitle: `Auto-mode \xB7 waiting for slot \xB7 ${sub.circuitName}`
|
|
2162
|
+
}
|
|
2163
|
+
),
|
|
2164
|
+
progressStrip,
|
|
2165
|
+
/* @__PURE__ */ jsx8(
|
|
2166
|
+
QueueView,
|
|
2167
|
+
{
|
|
2168
|
+
ceremonyId: activeCeremonyId,
|
|
2169
|
+
trackId: sub.trackId,
|
|
2170
|
+
token: session.session_token,
|
|
2171
|
+
onReady: (status) => {
|
|
2172
|
+
const entropy = deriveCircuitEntropy(
|
|
2173
|
+
screen.masterSeed,
|
|
2174
|
+
activeCeremonyId,
|
|
2175
|
+
sub.circuitName
|
|
2176
|
+
);
|
|
2177
|
+
setScreen({
|
|
2178
|
+
...screen,
|
|
2179
|
+
subPhase: {
|
|
2180
|
+
kind: "contributing",
|
|
2181
|
+
trackId: sub.trackId,
|
|
2182
|
+
circuitName: sub.circuitName,
|
|
2183
|
+
slotStatus: status,
|
|
2184
|
+
entropy
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
},
|
|
2188
|
+
onError: (e) => {
|
|
2189
|
+
const failedTrack = screen.current;
|
|
2190
|
+
const newDone = {
|
|
2191
|
+
trackId: failedTrack.id,
|
|
2192
|
+
circuitName: failedTrack.circuit_name,
|
|
2193
|
+
result: "failed",
|
|
2194
|
+
reason: e.message
|
|
2195
|
+
};
|
|
2196
|
+
setScreen({
|
|
2197
|
+
...screen,
|
|
2198
|
+
current: null,
|
|
2199
|
+
subPhase: { kind: "queueing" },
|
|
2200
|
+
done: [...screen.done, newDone],
|
|
2201
|
+
retryQueue: screen.retryingPhase ? screen.retryQueue : [...screen.retryQueue, failedTrack]
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
),
|
|
2206
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2207
|
+
"\u232B Backspace skip this circuit \xB7 ",
|
|
2208
|
+
QUIT_HINTS.abortRun
|
|
2209
|
+
] })
|
|
2210
|
+
] });
|
|
2211
|
+
}
|
|
2212
|
+
if (screen.subPhase.kind === "contributing") {
|
|
2213
|
+
const sub = screen.subPhase;
|
|
2214
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2215
|
+
/* @__PURE__ */ jsx8(
|
|
2216
|
+
Header,
|
|
2217
|
+
{
|
|
2218
|
+
ceremony,
|
|
2219
|
+
subtitle: `Auto-mode \xB7 contributing \xB7 ${sub.circuitName}`
|
|
2220
|
+
}
|
|
2221
|
+
),
|
|
2222
|
+
progressStrip,
|
|
2223
|
+
/* @__PURE__ */ jsx8(
|
|
2224
|
+
ContributeFlow,
|
|
2225
|
+
{
|
|
2226
|
+
ceremonyId: activeCeremonyId,
|
|
2227
|
+
trackId: sub.trackId,
|
|
2228
|
+
token: session.session_token,
|
|
2229
|
+
slotStatus: sub.slotStatus,
|
|
2230
|
+
entropy: sub.entropy,
|
|
2231
|
+
displayName: displayName2,
|
|
2232
|
+
onComplete: async (contributionId, receipt) => {
|
|
2233
|
+
const local = {
|
|
2234
|
+
contributionId,
|
|
2235
|
+
sequenceNumber: receipt?.sequence_number ?? 0,
|
|
2236
|
+
contributionHash: receipt?.contribution_hash ?? "",
|
|
2237
|
+
circuitName: sub.circuitName,
|
|
2238
|
+
ceremonyId: activeCeremonyId,
|
|
2239
|
+
verifiedAt: receipt?.verified_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2240
|
+
};
|
|
2241
|
+
await recordContribution(sub.trackId, local);
|
|
2242
|
+
setContributed((prev) => {
|
|
2243
|
+
const existing = prev[sub.trackId] ?? [];
|
|
2244
|
+
if (existing.some((c) => c.contributionId === local.contributionId)) return prev;
|
|
2245
|
+
return { ...prev, [sub.trackId]: [...existing, local] };
|
|
2246
|
+
});
|
|
2247
|
+
const successTrack = screen.current;
|
|
2248
|
+
setScreen({
|
|
2249
|
+
...screen,
|
|
2250
|
+
current: null,
|
|
2251
|
+
subPhase: { kind: "queueing" },
|
|
2252
|
+
done: [
|
|
2253
|
+
...screen.done,
|
|
2254
|
+
{
|
|
2255
|
+
trackId: successTrack.id,
|
|
2256
|
+
circuitName: successTrack.circuit_name,
|
|
2257
|
+
result: "verified",
|
|
2258
|
+
hash: local.contributionHash
|
|
2259
|
+
}
|
|
2260
|
+
]
|
|
2261
|
+
});
|
|
2262
|
+
},
|
|
2263
|
+
onError: (e) => {
|
|
2264
|
+
const failedTrack = screen.current;
|
|
2265
|
+
setScreen({
|
|
2266
|
+
...screen,
|
|
2267
|
+
current: null,
|
|
2268
|
+
subPhase: { kind: "queueing" },
|
|
2269
|
+
done: [
|
|
2270
|
+
...screen.done,
|
|
2271
|
+
{
|
|
2272
|
+
trackId: failedTrack.id,
|
|
2273
|
+
circuitName: failedTrack.circuit_name,
|
|
2274
|
+
result: "failed",
|
|
2275
|
+
reason: e.message
|
|
2276
|
+
}
|
|
2277
|
+
],
|
|
2278
|
+
retryQueue: screen.retryingPhase ? screen.retryQueue : [...screen.retryQueue, failedTrack]
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
)
|
|
2283
|
+
] });
|
|
2284
|
+
}
|
|
2285
|
+
return null;
|
|
2286
|
+
}
|
|
2287
|
+
if (screen.name === "auto-summary") {
|
|
2288
|
+
const verified = screen.outcomes.filter((o) => o.result === "verified");
|
|
2289
|
+
const failed = screen.outcomes.filter((o) => o.result === "failed");
|
|
2290
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2291
|
+
/* @__PURE__ */ jsx8(Header, { ceremony, subtitle: "Auto-mode \xB7 complete" }),
|
|
2292
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
|
|
2293
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: failed.length === 0 ? "greenBright" : "yellow", children: failed.length === 0 ? `\u2714 Auto-mode complete \u2014 ${verified.length} circuits verified` : `\u26A0 Auto-mode complete with ${failed.length} failure${failed.length === 1 ? "" : "s"}` }),
|
|
2294
|
+
verified.length > 0 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2295
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, color: "cyan", children: "Verified contributions:" }),
|
|
2296
|
+
verified.map((o, i) => /* @__PURE__ */ jsxs8(Box8, { paddingLeft: 2, children: [
|
|
2297
|
+
/* @__PURE__ */ jsx8(Text8, { color: "greenBright", children: "\u2714 " }),
|
|
2298
|
+
/* @__PURE__ */ jsx8(Text8, { color: "blueBright", children: elideMiddle(o.circuitName, 42).padEnd(44) }),
|
|
2299
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyanBright", children: o.hash ? o.hash.slice(0, 16) + "\u2026" : "" })
|
|
2300
|
+
] }, i))
|
|
2301
|
+
] }),
|
|
2302
|
+
failed.length > 0 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2303
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "Failed (retry already attempted):" }),
|
|
2304
|
+
failed.map((o, i) => /* @__PURE__ */ jsxs8(Box8, { paddingLeft: 2, flexDirection: "column", children: [
|
|
2305
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2306
|
+
/* @__PURE__ */ jsx8(Text8, { color: "red", children: "\u2717 " }),
|
|
2307
|
+
/* @__PURE__ */ jsx8(Text8, { children: elideMiddle(o.circuitName, 42) })
|
|
2308
|
+
] }),
|
|
2309
|
+
/* @__PURE__ */ jsx8(Box8, { paddingLeft: 4, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: o.reason ?? "(no reason recorded)" }) })
|
|
2310
|
+
] }, i)),
|
|
2311
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u232B Backspace / B \u2014 back to track list (you can retry failed circuits manually)" })
|
|
2312
|
+
] }),
|
|
2313
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: QUIT_HINTS.quit })
|
|
2314
|
+
] })
|
|
1311
2315
|
] });
|
|
1312
2316
|
}
|
|
1313
2317
|
if (screen.name === "error") {
|
|
1314
|
-
const backHint = !initialCeremonyId ?
|
|
1315
|
-
return /* @__PURE__ */
|
|
1316
|
-
/* @__PURE__ */
|
|
1317
|
-
/* @__PURE__ */
|
|
2318
|
+
const backHint = !initialCeremonyId ? `\u232B Backspace \u2014 back to ceremony list \xB7 ${QUIT_HINTS.quit}` : screen.recoverable ? `\u232B Backspace / B to go back \xB7 ${QUIT_HINTS.quit}` : QUIT_HINTS.quit;
|
|
2319
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2320
|
+
/* @__PURE__ */ jsx8(Header, { ceremony }),
|
|
2321
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "red", bold: true, children: [
|
|
1318
2322
|
"\u2717 ",
|
|
1319
2323
|
screen.message
|
|
1320
2324
|
] }),
|
|
1321
|
-
/* @__PURE__ */
|
|
2325
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: backHint })
|
|
1322
2326
|
] });
|
|
1323
2327
|
}
|
|
1324
2328
|
if (screen.name === "tracks") {
|
|
1325
2329
|
const { tracks } = screen;
|
|
1326
2330
|
const openTracks = tracks.filter((t) => t.status === "open");
|
|
1327
|
-
const myContributions = Object.values(contributed).filter(
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
/* @__PURE__ */
|
|
1334
|
-
/* @__PURE__ */
|
|
2331
|
+
const myContributions = Object.values(contributed).flat().filter((c) => c.ceremonyId === activeCeremonyId).sort((a, b) => {
|
|
2332
|
+
const ta = a.verifiedAt ? Date.parse(a.verifiedAt) : 0;
|
|
2333
|
+
const tb = b.verifiedAt ? Date.parse(b.verifiedAt) : 0;
|
|
2334
|
+
return tb - ta;
|
|
2335
|
+
});
|
|
2336
|
+
const TabBar = () => /* @__PURE__ */ jsxs8(Box8, { gap: 1, marginBottom: 1, children: [
|
|
2337
|
+
/* @__PURE__ */ jsx8(Text8, { bold: tab === 0, color: tab === 0 ? "cyan" : void 0, dimColor: tab !== 0, children: tab === 0 ? "[ Dashboard ]" : " Dashboard " }),
|
|
2338
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "|" }),
|
|
2339
|
+
/* @__PURE__ */ jsx8(Text8, { bold: tab === 1, color: tab === 1 ? "cyan" : void 0, dimColor: tab !== 1, children: tab === 1 ? `[ My Contributions (${myContributions.length}) ]` : ` My Contributions (${myContributions.length}) ` }),
|
|
2340
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " Tab to switch" })
|
|
1335
2341
|
] });
|
|
1336
|
-
return /* @__PURE__ */
|
|
1337
|
-
/* @__PURE__ */
|
|
1338
|
-
/* @__PURE__ */
|
|
2342
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2343
|
+
/* @__PURE__ */ jsx8(Header, { ceremony }),
|
|
2344
|
+
/* @__PURE__ */ jsx8(TabBar, {}),
|
|
1339
2345
|
tab === 0 ? (
|
|
1340
2346
|
// ── Dashboard tab ────────────────────────────────────────────────
|
|
1341
2347
|
// CIRCUIT column is sized to fit the longest mainnet name
|
|
@@ -1343,106 +2349,118 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1343
2349
|
// longer than that are middle-elided so the disambiguating suffix
|
|
1344
2350
|
// (-n1/-n2/-n4) stays visible — previously a hard slice(0,24)
|
|
1345
2351
|
// showed every claim variant as `claim-deposit-into-confi..`.
|
|
1346
|
-
/* @__PURE__ */
|
|
1347
|
-
/* @__PURE__ */
|
|
2352
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2353
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1348
2354
|
" CIRCUIT".padEnd(46),
|
|
1349
2355
|
"TOTAL".padEnd(8),
|
|
1350
2356
|
"QUEUE".padEnd(8),
|
|
1351
2357
|
"STATUS".padEnd(14),
|
|
1352
2358
|
"MY CONTRIBUTIONS"
|
|
1353
2359
|
] }),
|
|
1354
|
-
/* @__PURE__ */
|
|
2360
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " " + "\u2500".repeat(90) }),
|
|
1355
2361
|
tracks.map((t, i) => {
|
|
1356
2362
|
const isSelected = i === selectedIdx;
|
|
1357
2363
|
const canContribute = t.status === "open";
|
|
1358
2364
|
const nameDisplay = elideMiddle(t.circuit_name, 42);
|
|
1359
2365
|
const statusColor = t.status === "open" ? "green" : t.status === "finalized" ? "cyan" : "yellow";
|
|
1360
|
-
const
|
|
1361
|
-
return /* @__PURE__ */
|
|
1362
|
-
/* @__PURE__ */
|
|
2366
|
+
const myRounds = roundCount(contributed, t.id);
|
|
2367
|
+
return /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2368
|
+
/* @__PURE__ */ jsxs8(Text8, { color: isSelected ? "cyan" : canContribute ? void 0 : "gray", children: [
|
|
1363
2369
|
isSelected ? "\u25B6 " : " ",
|
|
1364
2370
|
nameDisplay.padEnd(44),
|
|
1365
2371
|
String(t.contribution_count).padEnd(8),
|
|
1366
2372
|
String(t.queue_depth).padEnd(8)
|
|
1367
2373
|
] }),
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
|
|
1370
|
-
"\u2713 contributed (
|
|
1371
|
-
|
|
1372
|
-
"
|
|
1373
|
-
|
|
1374
|
-
"\
|
|
1375
|
-
|
|
2374
|
+
/* @__PURE__ */ jsx8(Text8, { color: statusColor, children: trackStatusLabel(t.status).padEnd(14) }),
|
|
2375
|
+
myRounds > 0 ? canContribute && isSelected ? /* @__PURE__ */ jsxs8(Text8, { color: "green", children: [
|
|
2376
|
+
"\u2713 contributed (",
|
|
2377
|
+
myRounds,
|
|
2378
|
+
" round",
|
|
2379
|
+
myRounds === 1 ? "" : "s",
|
|
2380
|
+
") \u2014",
|
|
2381
|
+
" ",
|
|
2382
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "Enter to add more" })
|
|
2383
|
+
] }) : /* @__PURE__ */ jsxs8(Text8, { color: "green", children: [
|
|
2384
|
+
"\u2713 contributed (",
|
|
2385
|
+
myRounds,
|
|
2386
|
+
" round",
|
|
2387
|
+
myRounds === 1 ? "" : "s",
|
|
1376
2388
|
")"
|
|
1377
|
-
] }) : canContribute ? isSelected ? /* @__PURE__ */
|
|
2389
|
+
] }) : canContribute ? isSelected ? /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u2190 Enter to contribute" }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "not contributed" }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u2014" })
|
|
1378
2390
|
] }, t.id);
|
|
1379
2391
|
}),
|
|
1380
|
-
/* @__PURE__ */
|
|
1381
|
-
openTracks.length === 0 ? /* @__PURE__ */
|
|
1382
|
-
"\u2191/\u2193 select \xB7 Enter contribute \xB7 R refresh \xB7
|
|
2392
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " " + "\u2500".repeat(70) }),
|
|
2393
|
+
openTracks.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "No circuits are open for contributions right now \u2014 the ceremony may be closing or already complete." }) : /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2394
|
+
"\u2191/\u2193 select \xB7 Enter contribute \xB7 R refresh \xB7 ",
|
|
2395
|
+
QUIT_HINTS.quit,
|
|
1383
2396
|
!initialCeremonyId ? " \xB7 \u232B back to ceremony list" : ""
|
|
1384
2397
|
] })
|
|
1385
2398
|
] })
|
|
1386
2399
|
) : (
|
|
1387
2400
|
// ── My Contributions tab ─────────────────────────────────────────
|
|
1388
|
-
/* @__PURE__ */
|
|
1389
|
-
/* @__PURE__ */
|
|
1390
|
-
/* @__PURE__ */
|
|
1391
|
-
] }) : /* @__PURE__ */
|
|
1392
|
-
/* @__PURE__ */
|
|
2401
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: myContributions.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
|
|
2402
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "No contributions yet." }),
|
|
2403
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Switch to Dashboard tab and press Enter on a circuit to contribute." })
|
|
2404
|
+
] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2405
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1393
2406
|
" CIRCUIT".padEnd(44),
|
|
1394
2407
|
"ROUND".padEnd(7),
|
|
1395
2408
|
"VERIFIED AT".padEnd(22)
|
|
1396
2409
|
] }),
|
|
1397
|
-
/* @__PURE__ */
|
|
2410
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " " + "\u2500".repeat(90) }),
|
|
1398
2411
|
myContributions.map((c, i) => {
|
|
1399
2412
|
const isSel = i === contribCursor;
|
|
1400
|
-
return /* @__PURE__ */
|
|
1401
|
-
/* @__PURE__ */
|
|
1402
|
-
/* @__PURE__ */
|
|
1403
|
-
/* @__PURE__ */
|
|
1404
|
-
/* @__PURE__ */
|
|
1405
|
-
/* @__PURE__ */
|
|
2413
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2414
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2415
|
+
/* @__PURE__ */ jsx8(Text8, { color: isSel ? "cyan" : "green", children: isSel ? "\u25B6 " : " " }),
|
|
2416
|
+
/* @__PURE__ */ jsx8(Text8, { bold: isSel, children: elideMiddle(c.circuitName, 40).padEnd(42) }),
|
|
2417
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", children: ("#" + c.sequenceNumber).padEnd(7) }),
|
|
2418
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: c.verifiedAt ? new Date(c.verifiedAt).toLocaleString() : "\u2014" })
|
|
1406
2419
|
] }),
|
|
1407
|
-
/* @__PURE__ */
|
|
2420
|
+
/* @__PURE__ */ jsx8(Box8, { paddingLeft: 4, children: /* @__PURE__ */ jsxs8(Text8, { color: isSel ? "cyan" : "gray", dimColor: !isSel, children: [
|
|
1408
2421
|
"hash:",
|
|
1409
2422
|
" ",
|
|
1410
2423
|
c.contributionHash ? c.contributionHash : "(pending \u2014 verify still in flight)"
|
|
1411
2424
|
] }) })
|
|
1412
2425
|
] }, i);
|
|
1413
2426
|
}),
|
|
1414
|
-
/* @__PURE__ */
|
|
1415
|
-
copyToast ? /* @__PURE__ */
|
|
2427
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " " + "\u2500".repeat(90) }),
|
|
2428
|
+
copyToast ? /* @__PURE__ */ jsx8(Text8, { color: "green", children: copyToast }) : /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1416
2429
|
"Total: ",
|
|
1417
2430
|
myContributions.length,
|
|
1418
2431
|
" contribution",
|
|
1419
2432
|
myContributions.length !== 1 ? "s" : "",
|
|
1420
2433
|
" \xB7 ",
|
|
1421
|
-
"\u2191/\u2193 select \xB7 C copy hash \xB7 Tab switch \xB7
|
|
2434
|
+
"\u2191/\u2193 select \xB7 C copy hash \xB7 Tab switch \xB7 ",
|
|
2435
|
+
QUIT_HINTS.quit
|
|
1422
2436
|
] })
|
|
1423
2437
|
] }) })
|
|
1424
2438
|
)
|
|
1425
2439
|
] });
|
|
1426
2440
|
}
|
|
1427
2441
|
if (screen.name === "joining") {
|
|
1428
|
-
return /* @__PURE__ */
|
|
1429
|
-
/* @__PURE__ */
|
|
1430
|
-
/* @__PURE__ */
|
|
2442
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2443
|
+
/* @__PURE__ */ jsx8(Header, { ceremony }),
|
|
2444
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Joining queue..." }),
|
|
2445
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: QUIT_HINTS.quit }) })
|
|
1431
2446
|
] });
|
|
1432
2447
|
}
|
|
1433
2448
|
if (screen.name === "queue") {
|
|
1434
2449
|
const { trackId, circuitName } = screen;
|
|
1435
|
-
const
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
" ",
|
|
1443
|
-
|
|
2450
|
+
const priorRounds = roundCount(contributed, trackId);
|
|
2451
|
+
const lastContrib = latestContribution(contributed, trackId);
|
|
2452
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2453
|
+
/* @__PURE__ */ jsx8(Header, { ceremony, subtitle: `Circuit: ${circuitName}` }),
|
|
2454
|
+
priorRounds > 0 && lastContrib && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
|
|
2455
|
+
"\u26A0 You've contributed to this circuit ",
|
|
2456
|
+
priorRounds,
|
|
2457
|
+
" time",
|
|
2458
|
+
priorRounds === 1 ? "" : "s",
|
|
2459
|
+
" (latest: round #",
|
|
2460
|
+
lastContrib.sequenceNumber,
|
|
2461
|
+
"). Each additional round adds more entropy and strengthens the ceremony \u2014 go ahead."
|
|
1444
2462
|
] }) }),
|
|
1445
|
-
/* @__PURE__ */
|
|
2463
|
+
/* @__PURE__ */ jsx8(
|
|
1446
2464
|
QueueView,
|
|
1447
2465
|
{
|
|
1448
2466
|
ceremonyId: activeCeremonyId,
|
|
@@ -1452,27 +2470,42 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1452
2470
|
onError: (e) => setScreen({ name: "error", message: e.message, recoverable: true })
|
|
1453
2471
|
}
|
|
1454
2472
|
),
|
|
1455
|
-
/* @__PURE__ */
|
|
2473
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2474
|
+
"\u232B Backspace to go back \xB7 ",
|
|
2475
|
+
QUIT_HINTS.quit
|
|
2476
|
+
] })
|
|
1456
2477
|
] });
|
|
1457
2478
|
}
|
|
1458
2479
|
if (screen.name === "entropy") {
|
|
1459
2480
|
const { trackId, circuitName, slotStatus } = screen;
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
2481
|
+
const onComplete = (entropy) => setScreen({ name: "contribute", trackId, circuitName, slotStatus, entropy });
|
|
2482
|
+
const onError = (e) => setScreen({ name: "error", message: e.message, recoverable: false });
|
|
2483
|
+
if (entropyMode === "mouse") {
|
|
2484
|
+
return /* @__PURE__ */ jsx8(
|
|
2485
|
+
MouseEntropyCollector,
|
|
1464
2486
|
{
|
|
1465
|
-
|
|
1466
|
-
|
|
2487
|
+
title: `Your turn \xB7 ${circuitName} \u2014 draw entropy for this contribution`,
|
|
2488
|
+
onComplete,
|
|
2489
|
+
onError
|
|
1467
2490
|
}
|
|
1468
|
-
)
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2494
|
+
/* @__PURE__ */ jsx8(
|
|
2495
|
+
Header,
|
|
2496
|
+
{
|
|
2497
|
+
ceremony,
|
|
2498
|
+
subtitle: `Circuit: ${circuitName} \xB7 Type to collect entropy`
|
|
2499
|
+
}
|
|
2500
|
+
),
|
|
2501
|
+
/* @__PURE__ */ jsx8(EntropyCollector, { onComplete, onError })
|
|
1469
2502
|
] });
|
|
1470
2503
|
}
|
|
1471
2504
|
if (screen.name === "contribute") {
|
|
1472
2505
|
const { trackId, circuitName, slotStatus, entropy } = screen;
|
|
1473
|
-
return /* @__PURE__ */
|
|
1474
|
-
/* @__PURE__ */
|
|
1475
|
-
/* @__PURE__ */
|
|
2506
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2507
|
+
/* @__PURE__ */ jsx8(Header, { ceremony, subtitle: `Circuit: ${circuitName} \xB7 Contributing` }),
|
|
2508
|
+
/* @__PURE__ */ jsx8(
|
|
1476
2509
|
ContributeFlow,
|
|
1477
2510
|
{
|
|
1478
2511
|
ceremonyId: activeCeremonyId,
|
|
@@ -1491,7 +2524,13 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1491
2524
|
verifiedAt: receipt?.verified_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1492
2525
|
};
|
|
1493
2526
|
await recordContribution(trackId, local);
|
|
1494
|
-
setContributed((prev) =>
|
|
2527
|
+
setContributed((prev) => {
|
|
2528
|
+
const existing = prev[trackId] ?? [];
|
|
2529
|
+
if (existing.some((c) => c.contributionId === local.contributionId)) {
|
|
2530
|
+
return prev;
|
|
2531
|
+
}
|
|
2532
|
+
return { ...prev, [trackId]: [...existing, local] };
|
|
2533
|
+
});
|
|
1495
2534
|
setScreen({ name: "done", contribution: local });
|
|
1496
2535
|
},
|
|
1497
2536
|
onError: (e) => setScreen({ name: "error", message: e.message, recoverable: false })
|
|
@@ -1501,41 +2540,52 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1501
2540
|
}
|
|
1502
2541
|
if (screen.name === "done") {
|
|
1503
2542
|
const { contribution } = screen;
|
|
1504
|
-
return /* @__PURE__ */
|
|
1505
|
-
/* @__PURE__ */
|
|
1506
|
-
/* @__PURE__ */
|
|
1507
|
-
/* @__PURE__ */
|
|
2543
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2544
|
+
/* @__PURE__ */ jsx8(Header, { ceremony }),
|
|
2545
|
+
/* @__PURE__ */ jsx8(Attestation, { contribution }),
|
|
2546
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2547
|
+
"\u232B Backspace / B = contribute to another circuit \xB7 ",
|
|
2548
|
+
QUIT_HINTS.quit
|
|
2549
|
+
] }) })
|
|
1508
2550
|
] });
|
|
1509
2551
|
}
|
|
1510
2552
|
return null;
|
|
1511
2553
|
}
|
|
1512
2554
|
|
|
1513
2555
|
// src/index.tsx
|
|
1514
|
-
import { jsx as
|
|
2556
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1515
2557
|
var ceremonyId = process.env["CEREMONY_ID"] ?? "";
|
|
1516
2558
|
var displayName = process.env["CONTRIBUTOR_NAME"];
|
|
1517
2559
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
1518
2560
|
function restoreScreen() {
|
|
1519
2561
|
process.stdout.write("\x1B[?1049l");
|
|
1520
2562
|
}
|
|
1521
|
-
|
|
2563
|
+
var exiting = false;
|
|
2564
|
+
async function gracefulExit(code = 0, deliberate = false) {
|
|
2565
|
+
if (exiting) {
|
|
2566
|
+
process.exit(code);
|
|
2567
|
+
}
|
|
2568
|
+
exiting = true;
|
|
1522
2569
|
restoreScreen();
|
|
1523
2570
|
await runQueueCleanup();
|
|
2571
|
+
if (deliberate) {
|
|
2572
|
+
await runSessionCleanup();
|
|
2573
|
+
}
|
|
1524
2574
|
process.exit(code);
|
|
1525
2575
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
restoreScreen();
|
|
1529
|
-
process.exit(0);
|
|
1530
|
-
});
|
|
2576
|
+
globalThis.__ceremonyGracefulExit = gracefulExit;
|
|
2577
|
+
process.on("SIGTSTP", () => {
|
|
1531
2578
|
});
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
2579
|
+
var DELIBERATE_SIGNALS = /* @__PURE__ */ new Set(["SIGINT"]);
|
|
2580
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
2581
|
+
process.on(signal, () => {
|
|
2582
|
+
gracefulExit(0, DELIBERATE_SIGNALS.has(signal)).catch(() => {
|
|
2583
|
+
restoreScreen();
|
|
2584
|
+
process.exit(0);
|
|
2585
|
+
});
|
|
1536
2586
|
});
|
|
1537
|
-
}
|
|
1538
|
-
var { waitUntilExit } = render(/* @__PURE__ */
|
|
2587
|
+
}
|
|
2588
|
+
var { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, { ceremonyId, displayName }), {
|
|
1539
2589
|
exitOnCtrlC: false
|
|
1540
2590
|
});
|
|
1541
2591
|
await waitUntilExit();
|