@umbra-privacy/ceremony 0.2.5 → 0.2.8
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 +277 -61
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/components/App.tsx
|
|
7
|
-
import { useEffect as
|
|
7
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
8
8
|
import { Box as Box7, Text as Text7, useApp, useInput as useInput2 } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/cleanup.ts
|
|
@@ -315,22 +315,141 @@ function Header({ ceremony, subtitle }) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
// src/components/QueueView.tsx
|
|
318
|
-
import { useEffect, useRef, useState } from "react";
|
|
318
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
319
319
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
320
320
|
import Spinner from "ink-spinner";
|
|
321
|
-
|
|
321
|
+
|
|
322
|
+
// src/hooks/useCyclingMessage.ts
|
|
323
|
+
import { useEffect, useState } from "react";
|
|
324
|
+
function useCyclingMessage(messages, intervalMs = 2500, active = true) {
|
|
325
|
+
const [idx, setIdx] = useState(0);
|
|
326
|
+
useEffect(() => {
|
|
327
|
+
if (!active || messages.length <= 1) return;
|
|
328
|
+
const t = setInterval(() => setIdx((i) => (i + 1) % messages.length), intervalMs);
|
|
329
|
+
return () => clearInterval(t);
|
|
330
|
+
}, [active, intervalMs, messages.length]);
|
|
331
|
+
return messages[idx] ?? messages[0] ?? "";
|
|
332
|
+
}
|
|
333
|
+
var COMPUTING_MESSAGES = [
|
|
334
|
+
"summoning fresh entropy",
|
|
335
|
+
"shuffling response bytes",
|
|
336
|
+
"tickling bn128 pairings",
|
|
337
|
+
"applying tau locally",
|
|
338
|
+
"folding contribution proof",
|
|
339
|
+
"blinding the trapdoor",
|
|
340
|
+
"sealing the bellman response",
|
|
341
|
+
"asking the curve where it lives",
|
|
342
|
+
"weaving G1 points into the new delta",
|
|
343
|
+
"negotiating with the pairing engine",
|
|
344
|
+
"rotating the keypair commitment",
|
|
345
|
+
"checking that tau is non-zero (just in case)",
|
|
346
|
+
"evaluating polynomials over your secret",
|
|
347
|
+
"scrubbing the entropy buffer mid-flight",
|
|
348
|
+
"ratifying the contribution sequence",
|
|
349
|
+
"computing s \xB7 \u03C4 in G1",
|
|
350
|
+
"computing t \xB7 \u03C4 in G2",
|
|
351
|
+
"running the signature-of-knowledge",
|
|
352
|
+
"binding the transcript to your contribution",
|
|
353
|
+
"double-checking nothing is at infinity",
|
|
354
|
+
"compressing the response footprint",
|
|
355
|
+
"verifying your math one more time",
|
|
356
|
+
"stamping the public key bundle",
|
|
357
|
+
"writing the response file to disk",
|
|
358
|
+
"preparing to hand the response back to the worker",
|
|
359
|
+
"almost done \u2014 finalising your tau",
|
|
360
|
+
"this is the cryptography working hard for you",
|
|
361
|
+
"your secret never leaves this machine",
|
|
362
|
+
"if your power-21 circuit feels slow, blame BN254",
|
|
363
|
+
"snarkjs is single-threaded, give it a moment",
|
|
364
|
+
"trusted setup ceremonies are a team sport",
|
|
365
|
+
"the worker will verify everything you did"
|
|
366
|
+
];
|
|
367
|
+
var VERIFYING_MESSAGES = [
|
|
368
|
+
"worker pulling your response from S3",
|
|
369
|
+
"importing bellman contribution into a new zkey",
|
|
370
|
+
"checking the contribution's pairing proof",
|
|
371
|
+
"writing the next zkey",
|
|
372
|
+
"uploading the new zkey",
|
|
373
|
+
"anchoring contribution hash in the audit chain",
|
|
374
|
+
"almost there \u2014 finalising your receipt",
|
|
375
|
+
"parsing your response's wire format",
|
|
376
|
+
"validating every G1 point is on the curve",
|
|
377
|
+
"validating every G2 point is in the right subgroup",
|
|
378
|
+
"rebuilding the transcript hash chain",
|
|
379
|
+
"running the knowledge proof pairing",
|
|
380
|
+
"verifying the new delta_g1 matches its evidence",
|
|
381
|
+
"verifying the new delta_g2 matches its evidence",
|
|
382
|
+
"checking the L array against your contribution",
|
|
383
|
+
"checking the H array against your contribution",
|
|
384
|
+
"deriving Fiat-Shamir scalars for the batched check",
|
|
385
|
+
"running the random-linear-combination same-ratio",
|
|
386
|
+
"confirming the vKey fields were not altered",
|
|
387
|
+
"sequencing the new zkey for the next contributor",
|
|
388
|
+
"this is the part where every byte gets re-checked",
|
|
389
|
+
"soundness of the whole ceremony rides on this step",
|
|
390
|
+
"exporting the new bellman params for the next round",
|
|
391
|
+
"writing your contribution into the ceremony transcript",
|
|
392
|
+
"publishing the new audit chain entry",
|
|
393
|
+
"any tampering would have failed by now",
|
|
394
|
+
"if you see this, your contribution is provably honest",
|
|
395
|
+
"computing your contribution receipt",
|
|
396
|
+
"the math agrees \u2014 wrapping things up",
|
|
397
|
+
"finalising the verified state in Postgres",
|
|
398
|
+
"you did the cryptographic work \u2014 we just had to check"
|
|
399
|
+
];
|
|
400
|
+
var EXPORTING_MESSAGES = [
|
|
401
|
+
"preparing your challenge file",
|
|
402
|
+
"extracting bellman params from the current zkey",
|
|
403
|
+
"wrapping the zkey for handoff",
|
|
404
|
+
"stamping a sha256 over the challenge bundle",
|
|
405
|
+
"presigning your download URL",
|
|
406
|
+
"fetching the latest verified state for this circuit",
|
|
407
|
+
"downloading the previous contribution from S3",
|
|
408
|
+
"checking the previous contribution's hash",
|
|
409
|
+
"carving out a personal challenge for your tau",
|
|
410
|
+
"the worker is queuing your slot",
|
|
411
|
+
"uploading the challenge so you can download it",
|
|
412
|
+
"rotating the worker's S3 connection",
|
|
413
|
+
"binding your challenge to the audit chain",
|
|
414
|
+
"stamping the challenge metadata in Postgres",
|
|
415
|
+
"AWS SDK is recycling connection pools",
|
|
416
|
+
"your slot timer starts the moment this finishes",
|
|
417
|
+
"writing the challenge to a fresh S3 key",
|
|
418
|
+
"double-checking the integrity hash",
|
|
419
|
+
"if this takes >1 min, S3 is having a bad time",
|
|
420
|
+
"Fargate is parsing the current bellman params",
|
|
421
|
+
"verifying the previous contribution's chain link",
|
|
422
|
+
"this step has no contributor-side analogue",
|
|
423
|
+
"the worker does the boring work so you don't have to",
|
|
424
|
+
"any second now \u2014 challenge is almost ready",
|
|
425
|
+
"encoding the challenge in Bellman wire format",
|
|
426
|
+
"making sure the file size matches the spec",
|
|
427
|
+
"presign URL is signed for 1 hour",
|
|
428
|
+
"your download will be tamper-evident",
|
|
429
|
+
"the challenge SHA-256 will be on the public transcript",
|
|
430
|
+
"patience \u2014 this is the longest server-side step",
|
|
431
|
+
"almost there \u2014 your turn is moments away"
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
// src/components/QueueView.tsx
|
|
435
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
322
436
|
var POLL_FAST_MS = 5e3;
|
|
323
437
|
var POLL_SLOW_MS = 15e3;
|
|
438
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
439
|
+
"timed_out",
|
|
440
|
+
"failed",
|
|
441
|
+
"verified"
|
|
442
|
+
]);
|
|
324
443
|
function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }) {
|
|
325
|
-
const [status, setStatus] =
|
|
326
|
-
const [pollErr, setPollErr] =
|
|
327
|
-
const [tick, setTick] =
|
|
444
|
+
const [status, setStatus] = useState2(null);
|
|
445
|
+
const [pollErr, setPollErr] = useState2(null);
|
|
446
|
+
const [tick, setTick] = useState2(0);
|
|
328
447
|
const timeoutRef = useRef(null);
|
|
329
|
-
|
|
448
|
+
useEffect2(() => {
|
|
330
449
|
const id = setInterval(() => setTick((t) => (t + 1) % 4), 500);
|
|
331
450
|
return () => clearInterval(id);
|
|
332
451
|
}, []);
|
|
333
|
-
|
|
452
|
+
useEffect2(() => {
|
|
334
453
|
let cancelled = false;
|
|
335
454
|
async function poll() {
|
|
336
455
|
try {
|
|
@@ -342,6 +461,9 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
|
|
|
342
461
|
onReady(s);
|
|
343
462
|
return;
|
|
344
463
|
}
|
|
464
|
+
if (TERMINAL_STATUSES.has(s.status)) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
345
467
|
const interval = s.queue_position <= 2 ? POLL_FAST_MS : POLL_SLOW_MS;
|
|
346
468
|
timeoutRef.current = setTimeout(poll, interval);
|
|
347
469
|
} catch (err) {
|
|
@@ -367,6 +489,9 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
|
|
|
367
489
|
] })
|
|
368
490
|
] });
|
|
369
491
|
}
|
|
492
|
+
if (TERMINAL_STATUSES.has(status.status)) {
|
|
493
|
+
return /* @__PURE__ */ jsx2(TerminalSlotMessage, { status: status.status });
|
|
494
|
+
}
|
|
370
495
|
const waitMins = Math.ceil((status.estimated_wait_secs ?? 0) / 60);
|
|
371
496
|
const expiresAt = status.slot_expires_at ? new Date(status.slot_expires_at).toLocaleTimeString([], {
|
|
372
497
|
hour: "2-digit",
|
|
@@ -395,7 +520,7 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
|
|
|
395
520
|
] })
|
|
396
521
|
] })
|
|
397
522
|
] }),
|
|
398
|
-
status.status === "exporting" || status.status === "your_turn" || status.status === "ready_to_download" ? /* @__PURE__ */ jsx2(
|
|
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: [
|
|
399
524
|
"Another contributor is active",
|
|
400
525
|
expiresAt ? ` \xB7 slot expires at ${expiresAt}` : ""
|
|
401
526
|
] }) }) : status.queue_position > 1 ? (
|
|
@@ -419,9 +544,46 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
|
|
|
419
544
|
] })
|
|
420
545
|
] });
|
|
421
546
|
}
|
|
547
|
+
function TerminalSlotMessage({ status }) {
|
|
548
|
+
const lines = {
|
|
549
|
+
timed_out: {
|
|
550
|
+
headline: "Your slot expired before the challenge file was ready.",
|
|
551
|
+
detail: "This is usually a transient S3 hiccup on the worker side, not a problem with your machine. Press B or Backspace to return to the track list and rejoin the queue \u2014 it almost always succeeds the second time."
|
|
552
|
+
},
|
|
553
|
+
failed: {
|
|
554
|
+
headline: "The worker reported a failure on this contribution.",
|
|
555
|
+
detail: "Press B or Backspace to return to the track list. If this happens repeatedly on the same track, the admin needs to investigate."
|
|
556
|
+
},
|
|
557
|
+
verified: {
|
|
558
|
+
headline: "This contribution is already recorded as verified.",
|
|
559
|
+
detail: "Press B or Backspace to return to the track list and pick another track."
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
const key = status;
|
|
563
|
+
const msg = lines[key] ?? {
|
|
564
|
+
headline: `Contribution ended with status: ${status}.`,
|
|
565
|
+
detail: "Press B or Backspace to return to the track list."
|
|
566
|
+
};
|
|
567
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
|
|
568
|
+
/* @__PURE__ */ jsx2(Text2, { color: "yellow", bold: true, children: msg.headline }),
|
|
569
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: msg.detail })
|
|
570
|
+
] });
|
|
571
|
+
}
|
|
572
|
+
function ExportingMessage({ status }) {
|
|
573
|
+
const exportingMsg = useCyclingMessage(EXPORTING_MESSAGES, 2500, status === "exporting");
|
|
574
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: status === "exporting" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
575
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
|
|
576
|
+
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
|
|
577
|
+
" ",
|
|
578
|
+
exportingMsg,
|
|
579
|
+
"\u2026"
|
|
580
|
+
] }),
|
|
581
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Your slot is active. The worker is preparing the challenge file just for you." })
|
|
582
|
+
] }) : /* @__PURE__ */ jsx2(Text2, { color: "green", children: "Challenge ready \u2014 loading contribution flow\u2026" }) });
|
|
583
|
+
}
|
|
422
584
|
|
|
423
585
|
// src/components/EntropyCollector.tsx
|
|
424
|
-
import { useRef as useRef2, useState as
|
|
586
|
+
import { useRef as useRef2, useState as useState3 } from "react";
|
|
425
587
|
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
426
588
|
|
|
427
589
|
// src/entropy.ts
|
|
@@ -442,8 +604,8 @@ function buildEntropyFromKeystrokes(chars, timingsNs) {
|
|
|
442
604
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
443
605
|
var TARGET = 20;
|
|
444
606
|
function EntropyCollector({ onComplete, onError }) {
|
|
445
|
-
const [count, setCount] =
|
|
446
|
-
const [done, setDone] =
|
|
607
|
+
const [count, setCount] = useState3(0);
|
|
608
|
+
const [done, setDone] = useState3(false);
|
|
447
609
|
const charsRef = useRef2([]);
|
|
448
610
|
const timingsRef = useRef2([]);
|
|
449
611
|
const lastRef = useRef2(process.hrtime.bigint());
|
|
@@ -511,7 +673,7 @@ function EntropyCollector({ onComplete, onError }) {
|
|
|
511
673
|
}
|
|
512
674
|
|
|
513
675
|
// src/components/ContributeFlow.tsx
|
|
514
|
-
import { useEffect as
|
|
676
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
515
677
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
516
678
|
import Spinner2 from "ink-spinner";
|
|
517
679
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -550,8 +712,8 @@ var STEP_INDEX = {
|
|
|
550
712
|
};
|
|
551
713
|
function ContributeFlow(props) {
|
|
552
714
|
const { ceremonyId: ceremonyId2, trackId, token, slotStatus, entropy, displayName: displayName2 } = props;
|
|
553
|
-
const [step, setStep] =
|
|
554
|
-
|
|
715
|
+
const [step, setStep] = useState4({ name: "downloading", bytesReceived: 0, total: null });
|
|
716
|
+
useEffect4(() => {
|
|
555
717
|
let cancelled = false;
|
|
556
718
|
const challengePath = join4(tmpdir2(), `ceremony-challenge-${Date.now()}.mpcparams`);
|
|
557
719
|
let responsePath = null;
|
|
@@ -597,8 +759,8 @@ function ContributeFlow(props) {
|
|
|
597
759
|
await api.signalUploaded(ceremonyId2, trackId, slotStatus.contribution_id, token);
|
|
598
760
|
if (cancelled) return;
|
|
599
761
|
setStep({ name: "verifying", attempt: 1 });
|
|
600
|
-
const TOTAL_POLLS =
|
|
601
|
-
const FAST_POLLS =
|
|
762
|
+
const TOTAL_POLLS = 100;
|
|
763
|
+
const FAST_POLLS = 10;
|
|
602
764
|
let receipt = null;
|
|
603
765
|
let lastErr = null;
|
|
604
766
|
for (let i = 0; i < TOTAL_POLLS; i++) {
|
|
@@ -632,6 +794,8 @@ function ContributeFlow(props) {
|
|
|
632
794
|
};
|
|
633
795
|
}, []);
|
|
634
796
|
const currentIdx = STEP_INDEX[step.name] ?? 0;
|
|
797
|
+
const computingMsg = useCyclingMessage(COMPUTING_MESSAGES, 2500, step.name === "computing");
|
|
798
|
+
const verifyingMsg = useCyclingMessage(VERIFYING_MESSAGES, 3e3, step.name === "verifying");
|
|
635
799
|
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", gap: 1, children: STEP_LABELS.map((label, i) => {
|
|
636
800
|
const isDone = i < currentIdx;
|
|
637
801
|
const isActive = i === currentIdx;
|
|
@@ -655,17 +819,25 @@ function ContributeFlow(props) {
|
|
|
655
819
|
] });
|
|
656
820
|
}
|
|
657
821
|
if (isActive && step.name === "verifying") {
|
|
658
|
-
const elapsed = step.attempt <=
|
|
822
|
+
const elapsed = step.attempt <= 10 ? step.attempt * 3 : 10 * 3 + (step.attempt - 10) * 10;
|
|
659
823
|
const mins = Math.floor(elapsed / 60);
|
|
660
824
|
const secs = elapsed % 60;
|
|
661
825
|
const time = mins > 0 ? `${mins}m ${secs.toString().padStart(2, "0")}s` : `${secs}s`;
|
|
662
826
|
detail = /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
663
827
|
" ",
|
|
664
|
-
|
|
828
|
+
verifyingMsg,
|
|
829
|
+
"\u2026 (",
|
|
665
830
|
time,
|
|
666
831
|
" elapsed)"
|
|
667
832
|
] });
|
|
668
833
|
}
|
|
834
|
+
if (isActive && step.name === "computing") {
|
|
835
|
+
detail = /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
836
|
+
" ",
|
|
837
|
+
computingMsg,
|
|
838
|
+
"\u2026"
|
|
839
|
+
] });
|
|
840
|
+
}
|
|
669
841
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
670
842
|
/* @__PURE__ */ jsxs4(Box4, { gap: 2, children: [
|
|
671
843
|
indicator,
|
|
@@ -678,8 +850,7 @@ function ContributeFlow(props) {
|
|
|
678
850
|
children: [
|
|
679
851
|
label.charAt(0).toUpperCase() + label.slice(1),
|
|
680
852
|
" ",
|
|
681
|
-
isActive && label === "computing" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(entropy stays local)" })
|
|
682
|
-
isActive && label === "uploading" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(~544 bytes)" })
|
|
853
|
+
isActive && label === "computing" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(entropy stays local)" })
|
|
683
854
|
]
|
|
684
855
|
}
|
|
685
856
|
)
|
|
@@ -753,21 +924,33 @@ function InfoModal() {
|
|
|
753
924
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
754
925
|
var NAME_MAX_LEN = 100;
|
|
755
926
|
var NAME_VALID_RE = /^[\p{L}\p{N} _.\-]*$/u;
|
|
927
|
+
function elideMiddle(s, maxLen) {
|
|
928
|
+
if (s.length <= maxLen) return s;
|
|
929
|
+
const keepHead = Math.ceil((maxLen - 1) / 2);
|
|
930
|
+
const keepTail = Math.floor((maxLen - 1) / 2);
|
|
931
|
+
return s.slice(0, keepHead) + "\u2026" + s.slice(s.length - keepTail);
|
|
932
|
+
}
|
|
933
|
+
function copyToClipboardOSC52(value) {
|
|
934
|
+
const payload = Buffer.from(value, "utf8").toString("base64");
|
|
935
|
+
process.stdout.write(`\x1B]52;c;${payload}\x07`);
|
|
936
|
+
}
|
|
756
937
|
function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName }) {
|
|
757
938
|
const { exit } = useApp();
|
|
758
|
-
const [activeCeremonyId, setActiveCeremonyId] =
|
|
759
|
-
const [displayName2, setDisplayName] =
|
|
760
|
-
const [nameSet, setNameSet] =
|
|
761
|
-
const [screen, setScreen] =
|
|
939
|
+
const [activeCeremonyId, setActiveCeremonyId] = useState5(initialCeremonyId);
|
|
940
|
+
const [displayName2, setDisplayName] = useState5(initialDisplayName ?? "anonymous");
|
|
941
|
+
const [nameSet, setNameSet] = useState5(initialDisplayName !== void 0);
|
|
942
|
+
const [screen, setScreen] = useState5(
|
|
762
943
|
initialDisplayName === void 0 ? { name: "name-input", value: "" } : initialCeremonyId ? { name: "loading" } : { name: "ceremony-picker", ceremonies: [], loading: true }
|
|
763
944
|
);
|
|
764
|
-
const [ceremony, setCeremony] =
|
|
765
|
-
const [session, setSession] =
|
|
766
|
-
const [contributed, setContributed] =
|
|
767
|
-
const [selectedIdx, setSelectedIdx] =
|
|
768
|
-
const [tab, setTab] =
|
|
769
|
-
const [
|
|
770
|
-
|
|
945
|
+
const [ceremony, setCeremony] = useState5(null);
|
|
946
|
+
const [session, setSession] = useState5(null);
|
|
947
|
+
const [contributed, setContributed] = useState5({});
|
|
948
|
+
const [selectedIdx, setSelectedIdx] = useState5(0);
|
|
949
|
+
const [tab, setTab] = useState5(0);
|
|
950
|
+
const [contribCursor, setContribCursor] = useState5(0);
|
|
951
|
+
const [copyToast, setCopyToast] = useState5(null);
|
|
952
|
+
const [showInfo, setShowInfo] = useState5(false);
|
|
953
|
+
useEffect5(() => {
|
|
771
954
|
if (!nameSet) return;
|
|
772
955
|
if (!initialCeremonyId) {
|
|
773
956
|
loadCeremonies();
|
|
@@ -890,7 +1073,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
890
1073
|
setScreen({ name: "ceremony-picker", ceremonies: [], loading: true });
|
|
891
1074
|
loadCeremonies();
|
|
892
1075
|
}
|
|
893
|
-
|
|
1076
|
+
useEffect5(() => {
|
|
894
1077
|
if (screen.name === "queue" && session) {
|
|
895
1078
|
const { trackId } = screen;
|
|
896
1079
|
setQueueCleanup(() => {
|
|
@@ -986,6 +1169,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
986
1169
|
const { tracks } = screen;
|
|
987
1170
|
if (key.tab) {
|
|
988
1171
|
setTab((t) => (t + 1) % 2);
|
|
1172
|
+
setContribCursor(0);
|
|
989
1173
|
return;
|
|
990
1174
|
}
|
|
991
1175
|
if (tab === 0) {
|
|
@@ -1002,6 +1186,30 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1002
1186
|
if (t && t.status === "open" && !contributed[t.id]) joinTrack(t);
|
|
1003
1187
|
return;
|
|
1004
1188
|
}
|
|
1189
|
+
} else if (tab === 1) {
|
|
1190
|
+
const myContribs = Object.values(contributed).filter(
|
|
1191
|
+
(c) => c.ceremonyId === activeCeremonyId
|
|
1192
|
+
);
|
|
1193
|
+
if (key.upArrow) {
|
|
1194
|
+
setContribCursor((i) => Math.max(0, i - 1));
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (key.downArrow) {
|
|
1198
|
+
setContribCursor((i) => Math.min(myContribs.length - 1, i + 1));
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
if (q === "c") {
|
|
1202
|
+
const target = myContribs[contribCursor];
|
|
1203
|
+
if (target && target.contributionHash) {
|
|
1204
|
+
copyToClipboardOSC52(target.contributionHash);
|
|
1205
|
+
setCopyToast(`\u2713 Hash copied (round #${target.sequenceNumber})`);
|
|
1206
|
+
setTimeout(() => setCopyToast(null), 2e3);
|
|
1207
|
+
} else if (target) {
|
|
1208
|
+
setCopyToast("Nothing to copy \u2014 hash is still pending");
|
|
1209
|
+
setTimeout(() => setCopyToast(null), 2e3);
|
|
1210
|
+
}
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1005
1213
|
}
|
|
1006
1214
|
if (q === "r") {
|
|
1007
1215
|
goHome();
|
|
@@ -1130,29 +1338,34 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1130
1338
|
/* @__PURE__ */ jsx7(TabBar, {}),
|
|
1131
1339
|
tab === 0 ? (
|
|
1132
1340
|
// ── Dashboard tab ────────────────────────────────────────────────
|
|
1341
|
+
// CIRCUIT column is sized to fit the longest mainnet name
|
|
1342
|
+
// (`claim-deposit-into-confidential-amount-n4` = 41 chars). Names
|
|
1343
|
+
// longer than that are middle-elided so the disambiguating suffix
|
|
1344
|
+
// (-n1/-n2/-n4) stays visible — previously a hard slice(0,24)
|
|
1345
|
+
// showed every claim variant as `claim-deposit-into-confi..`.
|
|
1133
1346
|
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1134
1347
|
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1135
|
-
" CIRCUIT".padEnd(
|
|
1136
|
-
"TOTAL".padEnd(
|
|
1348
|
+
" CIRCUIT".padEnd(46),
|
|
1349
|
+
"TOTAL".padEnd(8),
|
|
1137
1350
|
"QUEUE".padEnd(8),
|
|
1138
|
-
"STATUS".padEnd(
|
|
1351
|
+
"STATUS".padEnd(14),
|
|
1139
1352
|
"MY CONTRIBUTIONS"
|
|
1140
1353
|
] }),
|
|
1141
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(
|
|
1354
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(90) }),
|
|
1142
1355
|
tracks.map((t, i) => {
|
|
1143
1356
|
const isSelected = i === selectedIdx;
|
|
1144
1357
|
const canContribute = t.status === "open";
|
|
1145
|
-
const
|
|
1358
|
+
const nameDisplay = elideMiddle(t.circuit_name, 42);
|
|
1146
1359
|
const statusColor = t.status === "open" ? "green" : t.status === "finalized" ? "cyan" : "yellow";
|
|
1147
1360
|
const myContrib = contributed[t.id];
|
|
1148
1361
|
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1149
1362
|
/* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : canContribute ? void 0 : "gray", children: [
|
|
1150
1363
|
isSelected ? "\u25B6 " : " ",
|
|
1151
|
-
|
|
1152
|
-
String(t.contribution_count).padEnd(
|
|
1364
|
+
nameDisplay.padEnd(44),
|
|
1365
|
+
String(t.contribution_count).padEnd(8),
|
|
1153
1366
|
String(t.queue_depth).padEnd(8)
|
|
1154
1367
|
] }),
|
|
1155
|
-
/* @__PURE__ */ jsx7(Text7, { color: statusColor, children: trackStatusLabel(t.status).padEnd(
|
|
1368
|
+
/* @__PURE__ */ jsx7(Text7, { color: statusColor, children: trackStatusLabel(t.status).padEnd(14) }),
|
|
1156
1369
|
myContrib ? isSelected ? /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
|
|
1157
1370
|
"\u2713 contributed (round #",
|
|
1158
1371
|
myContrib.sequenceNumber,
|
|
@@ -1177,32 +1390,35 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
|
|
|
1177
1390
|
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Switch to Dashboard tab and press Enter on a circuit to contribute." })
|
|
1178
1391
|
] }) : /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1179
1392
|
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1180
|
-
" CIRCUIT".padEnd(
|
|
1181
|
-
"ROUND".padEnd(
|
|
1182
|
-
"
|
|
1183
|
-
"TIME"
|
|
1393
|
+
" CIRCUIT".padEnd(44),
|
|
1394
|
+
"ROUND".padEnd(7),
|
|
1395
|
+
"VERIFIED AT".padEnd(22)
|
|
1184
1396
|
] }),
|
|
1185
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(
|
|
1186
|
-
myContributions.map((c, i) =>
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
/* @__PURE__ */
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1397
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(90) }),
|
|
1398
|
+
myContributions.map((c, i) => {
|
|
1399
|
+
const isSel = i === contribCursor;
|
|
1400
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1401
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1402
|
+
/* @__PURE__ */ jsx7(Text7, { color: isSel ? "cyan" : "green", children: isSel ? "\u25B6 " : " " }),
|
|
1403
|
+
/* @__PURE__ */ jsx7(Text7, { bold: isSel, children: elideMiddle(c.circuitName, 40).padEnd(42) }),
|
|
1404
|
+
/* @__PURE__ */ jsx7(Text7, { color: "yellow", children: ("#" + c.sequenceNumber).padEnd(7) }),
|
|
1405
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: c.verifiedAt ? new Date(c.verifiedAt).toLocaleString() : "\u2014" })
|
|
1406
|
+
] }),
|
|
1407
|
+
/* @__PURE__ */ jsx7(Box7, { paddingLeft: 4, children: /* @__PURE__ */ jsxs7(Text7, { color: isSel ? "cyan" : "gray", dimColor: !isSel, children: [
|
|
1408
|
+
"hash:",
|
|
1409
|
+
" ",
|
|
1410
|
+
c.contributionHash ? c.contributionHash : "(pending \u2014 verify still in flight)"
|
|
1411
|
+
] }) })
|
|
1412
|
+
] }, i);
|
|
1413
|
+
}),
|
|
1414
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(90) }),
|
|
1415
|
+
copyToast ? /* @__PURE__ */ jsx7(Text7, { color: "green", children: copyToast }) : /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1200
1416
|
"Total: ",
|
|
1201
1417
|
myContributions.length,
|
|
1202
1418
|
" contribution",
|
|
1203
1419
|
myContributions.length !== 1 ? "s" : "",
|
|
1204
1420
|
" \xB7 ",
|
|
1205
|
-
"Tab
|
|
1421
|
+
"\u2191/\u2193 select \xB7 C copy hash \xB7 Tab switch \xB7 Q quit"
|
|
1206
1422
|
] })
|
|
1207
1423
|
] }) })
|
|
1208
1424
|
)
|