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