@umbra-privacy/ceremony 0.2.3 → 0.2.5

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.
Files changed (2) hide show
  1. package/dist/index.js +137 -75
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -90,26 +90,20 @@ var api = {
90
90
  },
91
91
  // Slot polling
92
92
  myTurn(ceremonyId2, trackId, token) {
93
- return request(
94
- `/api/ceremonies/${ceremonyId2}/tracks/${trackId}/my-turn`,
95
- { headers: bearer(token) }
96
- );
93
+ return request(`/api/ceremonies/${ceremonyId2}/tracks/${trackId}/my-turn`, {
94
+ headers: bearer(token)
95
+ });
97
96
  },
98
97
  // Contribution
99
98
  signalUploaded(ceremonyId2, trackId, contributionId, token) {
100
- return request(
101
- `/api/ceremonies/${ceremonyId2}/tracks/${trackId}/signal-uploaded`,
102
- {
103
- method: "POST",
104
- headers: bearer(token),
105
- body: JSON.stringify({ contribution_id: contributionId })
106
- }
107
- );
99
+ return request(`/api/ceremonies/${ceremonyId2}/tracks/${trackId}/signal-uploaded`, {
100
+ method: "POST",
101
+ headers: bearer(token),
102
+ body: JSON.stringify({ contribution_id: contributionId })
103
+ });
108
104
  },
109
105
  getReceipt(ceremonyId2, contributionId) {
110
- return request(
111
- `/api/ceremonies/${ceremonyId2}/contributions/${contributionId}/receipt`
112
- );
106
+ return request(`/api/ceremonies/${ceremonyId2}/contributions/${contributionId}/receipt`);
113
107
  },
114
108
  // Admin — signed-request flow. Caller supplies the 64-byte Solana keypair
115
109
  // bytes (32 secret + 32 public). The helper fetches a one-time challenge,
@@ -252,6 +246,38 @@ async function recordContribution(trackId, contribution) {
252
246
 
253
247
  // src/components/Header.tsx
254
248
  import { Box, Text } from "ink";
249
+
250
+ // src/labels.ts
251
+ function ceremonyStatusLabel(status) {
252
+ switch (status) {
253
+ case "open":
254
+ return "open";
255
+ case "initialized":
256
+ return "not yet open";
257
+ case "finalizing":
258
+ return "closing";
259
+ case "completed":
260
+ return "complete";
261
+ default:
262
+ return status;
263
+ }
264
+ }
265
+ function trackStatusLabel(status) {
266
+ switch (status) {
267
+ case "open":
268
+ return "open";
269
+ case "initializing":
270
+ return "preparing";
271
+ case "awaiting_beacon":
272
+ return "awaiting beacon";
273
+ case "finalized":
274
+ return "complete";
275
+ default:
276
+ return status;
277
+ }
278
+ }
279
+
280
+ // src/components/Header.tsx
255
281
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
256
282
  var STATUS_COLOR = {
257
283
  open: "green",
@@ -264,10 +290,10 @@ function Header({ ceremony, subtitle }) {
264
290
  /* @__PURE__ */ jsxs(Box, { children: [
265
291
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u25C6 Umbra Ceremony TUI" }),
266
292
  ceremony && /* @__PURE__ */ jsxs(Fragment, { children: [
267
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
293
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
268
294
  /* @__PURE__ */ jsx(Text, { bold: true, children: ceremony.name }),
269
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " [" }),
270
- /* @__PURE__ */ jsx(Text, { color: STATUS_COLOR[ceremony.status] ?? "white", children: ceremony.status }),
295
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " [" }),
296
+ /* @__PURE__ */ jsx(Text, { color: STATUS_COLOR[ceremony.status] ?? "white", children: ceremonyStatusLabel(ceremony.status) }),
271
297
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "]" })
272
298
  ] })
273
299
  ] }),
@@ -342,7 +368,10 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
342
368
  ] });
343
369
  }
344
370
  const waitMins = Math.ceil((status.estimated_wait_secs ?? 0) / 60);
345
- const expiresAt = status.slot_expires_at ? new Date(status.slot_expires_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : null;
371
+ const expiresAt = status.slot_expires_at ? new Date(status.slot_expires_at).toLocaleTimeString([], {
372
+ hour: "2-digit",
373
+ minute: "2-digit"
374
+ }) : null;
346
375
  const fastPoll = status.queue_position <= 2;
347
376
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
348
377
  /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
@@ -372,11 +401,7 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
372
401
  ] }) }) : status.queue_position > 1 ? (
373
402
  // Slot is idle but people are ahead — they joined and left without releasing.
374
403
  // timeout_watchdog will clear each stale slot after contribution_timeout_secs.
375
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
376
- "Slot is idle \u2014 waiting for positions ahead to respond or time out",
377
- " ",
378
- "(up to ~5 min each)"
379
- ] })
404
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Slot is idle \u2014 waiting for positions ahead to respond or time out (up to ~5 min each)" })
380
405
  ) : (
381
406
  // Position 1, slot idle — advance_queue should fire shortly.
382
407
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Slot is idle \u2014 your turn is being prepared..." })
@@ -460,7 +485,7 @@ function EntropyCollector({ onComplete, onError }) {
460
485
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Type anything to generate entropy:" }),
461
486
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Keystroke timing (nanosecond intervals) is the randomness source." }),
462
487
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
463
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " > " }),
488
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " > " }),
464
489
  /* @__PURE__ */ jsx3(Text3, { color: "green", children: stars }),
465
490
  /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "\u2588" })
466
491
  ] }),
@@ -475,7 +500,7 @@ function EntropyCollector({ onComplete, onError }) {
475
500
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
476
501
  "/",
477
502
  TARGET,
478
- " keystrokes (",
503
+ " keystrokes (",
479
504
  pct,
480
505
  "%)"
481
506
  ] })
@@ -572,9 +597,11 @@ function ContributeFlow(props) {
572
597
  await api.signalUploaded(ceremonyId2, trackId, slotStatus.contribution_id, token);
573
598
  if (cancelled) return;
574
599
  setStep({ name: "verifying", attempt: 1 });
600
+ const TOTAL_POLLS = 80;
601
+ const FAST_POLLS = 20;
575
602
  let receipt = null;
576
603
  let lastErr = null;
577
- for (let i = 0; i < 30; i++) {
604
+ for (let i = 0; i < TOTAL_POLLS; i++) {
578
605
  if (cancelled) return;
579
606
  setStep({ name: "verifying", attempt: i + 1 });
580
607
  try {
@@ -583,7 +610,8 @@ function ContributeFlow(props) {
583
610
  break;
584
611
  } catch (err) {
585
612
  lastErr = err instanceof Error ? err : new Error(String(err));
586
- await new Promise((r) => setTimeout(r, 3e3));
613
+ const delay = i < FAST_POLLS ? 3e3 : 1e4;
614
+ await new Promise((r) => setTimeout(r, delay));
587
615
  }
588
616
  }
589
617
  if (cancelled) return;
@@ -618,7 +646,8 @@ function ContributeFlow(props) {
618
646
  const filled = Math.round(pct / 5);
619
647
  const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
620
648
  detail = /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
621
- " [",
649
+ " ",
650
+ "[",
622
651
  bar,
623
652
  "] ",
624
653
  pct,
@@ -626,10 +655,15 @@ function ContributeFlow(props) {
626
655
  ] });
627
656
  }
628
657
  if (isActive && step.name === "verifying") {
658
+ const elapsed = step.attempt <= 20 ? step.attempt * 3 : 20 * 3 + (step.attempt - 20) * 10;
659
+ const mins = Math.floor(elapsed / 60);
660
+ const secs = elapsed % 60;
661
+ const time = mins > 0 ? `${mins}m ${secs.toString().padStart(2, "0")}s` : `${secs}s`;
629
662
  detail = /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
630
- " worker verifying... (",
631
- step.attempt,
632
- "/30)"
663
+ " ",
664
+ "worker verifying \u2014 large circuits can take a few minutes (",
665
+ time,
666
+ " elapsed)"
633
667
  ] });
634
668
  }
635
669
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
@@ -662,25 +696,25 @@ function Attestation({ contribution }) {
662
696
  const hashShort = contribution.contributionHash ? `${contribution.contributionHash.slice(0, 16)}...${contribution.contributionHash.slice(-8)}` : "(pending verification)";
663
697
  const verifiedAt = contribution.verifiedAt ? new Date(contribution.verifiedAt).toLocaleString() : null;
664
698
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
665
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "green", children: "\u2713 Contribution verified!" }),
699
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: "green", children: "\u2713 Contribution verified!" }),
666
700
  /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 0, children: [
667
701
  /* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
668
702
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Circuit" }),
669
703
  /* @__PURE__ */ jsx5(Text5, { bold: true, children: contribution.circuitName })
670
704
  ] }),
671
705
  /* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
672
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Round " }),
706
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Round " }),
673
707
  /* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
674
708
  "#",
675
709
  contribution.sequenceNumber
676
710
  ] })
677
711
  ] }),
678
712
  /* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
679
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Hash " }),
713
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Hash " }),
680
714
  /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: hashShort })
681
715
  ] }),
682
716
  verifiedAt && /* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
683
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Time " }),
717
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Time " }),
684
718
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: verifiedAt })
685
719
  ] })
686
720
  ] }),
@@ -747,7 +781,11 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
747
781
  setScreen({ name: "ceremony-picker", ceremonies, loading: false });
748
782
  setSelectedIdx(0);
749
783
  } catch (e) {
750
- setScreen({ name: "error", message: e.message ?? "Failed to load ceremonies.", recoverable: false });
784
+ setScreen({
785
+ name: "error",
786
+ message: e.message ?? "Failed to load ceremonies.",
787
+ recoverable: false
788
+ });
751
789
  }
752
790
  }
753
791
  async function boot(cId) {
@@ -769,7 +807,11 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
769
807
  try {
770
808
  const { tracks } = await api.listTracks(cId, s.session_token);
771
809
  if (tracks.length === 0) {
772
- setScreen({ name: "error", message: "No tracks found in this ceremony.", recoverable: false });
810
+ setScreen({
811
+ name: "error",
812
+ message: "No tracks found in this ceremony.",
813
+ recoverable: false
814
+ });
773
815
  return;
774
816
  }
775
817
  setSelectedIdx(0);
@@ -782,17 +824,29 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
782
824
  setSession(fresh);
783
825
  const { tracks } = await api.listTracks(cId, fresh.session_token);
784
826
  if (tracks.length === 0) {
785
- setScreen({ name: "error", message: "No tracks found in this ceremony.", recoverable: false });
827
+ setScreen({
828
+ name: "error",
829
+ message: "No tracks found in this ceremony.",
830
+ recoverable: false
831
+ });
786
832
  return;
787
833
  }
788
834
  setSelectedIdx(0);
789
835
  setScreen({ name: "tracks", tracks });
790
836
  } catch (e2) {
791
- setScreen({ name: "error", message: e2.message ?? "Failed to load tracks.", recoverable: false });
837
+ setScreen({
838
+ name: "error",
839
+ message: e2.message ?? "Failed to load tracks.",
840
+ recoverable: false
841
+ });
792
842
  }
793
843
  return;
794
844
  }
795
- setScreen({ name: "error", message: e.message ?? "Failed to load tracks.", recoverable: false });
845
+ setScreen({
846
+ name: "error",
847
+ message: e.message ?? "Failed to load tracks.",
848
+ recoverable: false
849
+ });
796
850
  }
797
851
  }
798
852
  function commitName(raw) {
@@ -805,10 +859,9 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
805
859
  );
806
860
  }
807
861
  function randomAnonName() {
808
- const hex = Array.from(
809
- { length: 6 },
810
- () => Math.floor(Math.random() * 16).toString(16)
811
- ).join("");
862
+ const hex = Array.from({ length: 6 }, () => Math.floor(Math.random() * 16).toString(16)).join(
863
+ ""
864
+ );
812
865
  return `anon-${hex}`;
813
866
  }
814
867
  async function joinTrack(track) {
@@ -818,7 +871,11 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
818
871
  await api.joinQueue(activeCeremonyId, track.id, session.session_token);
819
872
  } catch (e) {
820
873
  if (e.code !== "ALREADY_IN_QUEUE") {
821
- setScreen({ name: "error", message: e.message ?? "Failed to join queue.", recoverable: true });
874
+ setScreen({
875
+ name: "error",
876
+ message: e.message ?? "Failed to join queue.",
877
+ recoverable: true
878
+ });
822
879
  return;
823
880
  }
824
881
  }
@@ -990,6 +1047,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
990
1047
  const isSelected = i === selectedIdx;
991
1048
  const isOpen = c.status === "open";
992
1049
  const statusColor = c.status === "open" ? "green" : c.status === "completed" ? "cyan" : "yellow";
1050
+ const statusLabel = ceremonyStatusLabel(c.status);
993
1051
  return /* @__PURE__ */ jsxs7(Box7, { gap: 2, children: [
994
1052
  /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : isOpen ? void 0 : "gray", children: [
995
1053
  isSelected ? "\u25B6 " : " ",
@@ -997,7 +1055,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
997
1055
  ] }),
998
1056
  /* @__PURE__ */ jsxs7(Text7, { color: statusColor, children: [
999
1057
  "[",
1000
- c.status,
1058
+ statusLabel,
1001
1059
  "]"
1002
1060
  ] }),
1003
1061
  /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
@@ -1017,20 +1075,23 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1017
1075
  if (!c) return null;
1018
1076
  if (c.status === "initialized") {
1019
1077
  return /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
1020
- " Not open yet \u2014 admin: run initialize ",
1021
- "<id>",
1022
- " then open ",
1023
- "<id>",
1024
- " to start contributions."
1078
+ " ",
1079
+ "This ceremony is not yet open for contributions. The Umbra team is still preparing the circuits \u2014 please check back shortly."
1025
1080
  ] });
1026
1081
  }
1027
1082
  if (c.status === "finalizing") {
1028
- return /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: " Contribution phase is closed \u2014 ceremony is applying the final beacon." });
1083
+ return /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
1084
+ " ",
1085
+ "Contributions are closed. The ceremony is computing the final verification key \u2014 no further contributions can be added."
1086
+ ] });
1029
1087
  }
1030
1088
  if (c.status === "completed") {
1031
- return /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: " Ceremony complete \u2014 verification keys are available." });
1089
+ return /* @__PURE__ */ jsxs7(Text7, { color: "cyan", children: [
1090
+ " ",
1091
+ "Ceremony complete. The verification keys are finalised \u2014 thank you to everyone who contributed."
1092
+ ] });
1032
1093
  }
1033
- return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2191/\u2193 select \xB7 Enter join \xB7 Q quit" });
1094
+ return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2191/\u2193 select \xB7 Enter join \xB7 Q quit" });
1034
1095
  })()
1035
1096
  ] })
1036
1097
  ] });
@@ -1046,7 +1107,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1046
1107
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1047
1108
  /* @__PURE__ */ jsx7(Header, { ceremony }),
1048
1109
  /* @__PURE__ */ jsxs7(Text7, { color: "red", bold: true, children: [
1049
- "\u2717 ",
1110
+ "\u2717 ",
1050
1111
  screen.message
1051
1112
  ] }),
1052
1113
  /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: backHint })
@@ -1055,12 +1116,14 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1055
1116
  if (screen.name === "tracks") {
1056
1117
  const { tracks } = screen;
1057
1118
  const openTracks = tracks.filter((t) => t.status === "open");
1058
- const myContributions = Object.values(contributed).filter((c) => c.ceremonyId === activeCeremonyId);
1119
+ const myContributions = Object.values(contributed).filter(
1120
+ (c) => c.ceremonyId === activeCeremonyId
1121
+ );
1059
1122
  const TabBar = () => /* @__PURE__ */ jsxs7(Box7, { gap: 1, marginBottom: 1, children: [
1060
1123
  /* @__PURE__ */ jsx7(Text7, { bold: tab === 0, color: tab === 0 ? "cyan" : void 0, dimColor: tab !== 0, children: tab === 0 ? "[ Dashboard ]" : " Dashboard " }),
1061
1124
  /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "|" }),
1062
1125
  /* @__PURE__ */ jsx7(Text7, { bold: tab === 1, color: tab === 1 ? "cyan" : void 0, dimColor: tab !== 1, children: tab === 1 ? `[ My Contributions (${myContributions.length}) ]` : ` My Contributions (${myContributions.length}) ` }),
1063
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " Tab to switch" })
1126
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " Tab to switch" })
1064
1127
  ] });
1065
1128
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1066
1129
  /* @__PURE__ */ jsx7(Header, { ceremony }),
@@ -1072,10 +1135,10 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1072
1135
  " CIRCUIT".padEnd(30),
1073
1136
  "TOTAL".padEnd(10),
1074
1137
  "QUEUE".padEnd(8),
1075
- "STATUS".padEnd(14),
1138
+ "STATUS".padEnd(16),
1076
1139
  "MY CONTRIBUTIONS"
1077
1140
  ] }),
1078
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(68) }),
1141
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(70) }),
1079
1142
  tracks.map((t, i) => {
1080
1143
  const isSelected = i === selectedIdx;
1081
1144
  const canContribute = t.status === "open";
@@ -1089,21 +1152,21 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1089
1152
  String(t.contribution_count).padEnd(10),
1090
1153
  String(t.queue_depth).padEnd(8)
1091
1154
  ] }),
1092
- /* @__PURE__ */ jsx7(Text7, { color: statusColor, children: t.status.padEnd(14) }),
1155
+ /* @__PURE__ */ jsx7(Text7, { color: statusColor, children: trackStatusLabel(t.status).padEnd(16) }),
1093
1156
  myContrib ? isSelected ? /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
1094
1157
  "\u2713 contributed (round #",
1095
1158
  myContrib.sequenceNumber,
1096
- ") \u2014 already done"
1159
+ ") \u2014 already done"
1097
1160
  ] }) : /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
1098
- "\u2713 contributed (round #",
1161
+ "\u2713 contributed (round #",
1099
1162
  myContrib.sequenceNumber,
1100
1163
  ")"
1101
1164
  ] }) : canContribute ? isSelected ? /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "\u2190 Enter to contribute" }) : /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "not contributed" }) : /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2014" })
1102
1165
  ] }, t.id);
1103
1166
  }),
1104
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(68) }),
1105
- openTracks.length === 0 ? /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "No open tracks \u2014 ceremony may be finalizing or complete." }) : /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1106
- "\u2191/\u2193 select \xB7 Enter contribute \xB7 R refresh \xB7 Q quit",
1167
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(70) }),
1168
+ openTracks.length === 0 ? /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "No circuits are open for contributions right now \u2014 the ceremony may be closing or already complete." }) : /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1169
+ "\u2191/\u2193 select \xB7 Enter contribute \xB7 R refresh \xB7 Q quit",
1107
1170
  !initialCeremonyId ? " \xB7 \u232B back to ceremony list" : ""
1108
1171
  ] })
1109
1172
  ] })
@@ -1122,7 +1185,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1122
1185
  /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(68) }),
1123
1186
  myContributions.map((c, i) => /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1124
1187
  /* @__PURE__ */ jsxs7(Box7, { children: [
1125
- /* @__PURE__ */ jsx7(Text7, { color: "green", children: " \u2713 " }),
1188
+ /* @__PURE__ */ jsx7(Text7, { color: "green", children: " \u2713 " }),
1126
1189
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: c.circuitName.padEnd(24) }),
1127
1190
  /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "#" + c.sequenceNumber + " " }),
1128
1191
  /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: c.contributionHash ? c.contributionHash.slice(0, 16) + "..." : "(pending)" })
@@ -1139,7 +1202,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1139
1202
  " contribution",
1140
1203
  myContributions.length !== 1 ? "s" : "",
1141
1204
  " \xB7 ",
1142
- "Tab to switch \xB7 Q to quit"
1205
+ "Tab to switch \xB7 Q to quit"
1143
1206
  ] })
1144
1207
  ] }) })
1145
1208
  )
@@ -1157,7 +1220,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1157
1220
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1158
1221
  /* @__PURE__ */ jsx7(Header, { ceremony, subtitle: `Circuit: ${circuitName}` }),
1159
1222
  priorContrib && /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, paddingX: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
1160
- "\u26A0 You already contributed to this circuit (round #",
1223
+ "\u26A0 You already contributed to this circuit (round #",
1161
1224
  priorContrib.sequenceNumber,
1162
1225
  ").",
1163
1226
  " ",
@@ -1173,7 +1236,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1173
1236
  onError: (e) => setScreen({ name: "error", message: e.message, recoverable: true })
1174
1237
  }
1175
1238
  ),
1176
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u232B Backspace to go back \xB7 Q to quit" })
1239
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u232B Backspace to go back \xB7 Q to quit" })
1177
1240
  ] });
1178
1241
  }
1179
1242
  if (screen.name === "entropy") {
@@ -1225,7 +1288,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1225
1288
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1226
1289
  /* @__PURE__ */ jsx7(Header, { ceremony }),
1227
1290
  /* @__PURE__ */ jsx7(Attestation, { contribution }),
1228
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u232B Backspace / B = contribute to another circuit \xB7 Q to quit" }) })
1291
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u232B Backspace / B = contribute to another circuit \xB7 Q to quit" }) })
1229
1292
  ] });
1230
1293
  }
1231
1294
  return null;
@@ -1256,10 +1319,9 @@ process.on("SIGTERM", () => {
1256
1319
  process.exit(0);
1257
1320
  });
1258
1321
  });
1259
- var { waitUntilExit } = render(
1260
- /* @__PURE__ */ jsx8(App, { ceremonyId, displayName }),
1261
- { exitOnCtrlC: false }
1262
- );
1322
+ var { waitUntilExit } = render(/* @__PURE__ */ jsx8(App, { ceremonyId, displayName }), {
1323
+ exitOnCtrlC: false
1324
+ });
1263
1325
  await waitUntilExit();
1264
1326
  restoreScreen();
1265
1327
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umbra-privacy/ceremony",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Terminal UI for the Umbra Phase 2 trusted setup ceremony",
5
5
  "type": "module",
6
6
  "bin": {