@orangecheck/vote-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OrangeCheck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # @orangecheck/vote-cli
2
+
3
+ Command-line tool for [OC Vote](https://github.com/orangecheck/oc-vote-protocol).
4
+
5
+ ```bash
6
+ npm i -g @orangecheck/vote-cli
7
+ ```
8
+
9
+ or run one-shot:
10
+
11
+ ```bash
12
+ npx -y @orangecheck/vote-cli tally <poll_id>
13
+ ```
14
+
15
+ ## Commands
16
+
17
+ ### `oc-vote tally <poll_id>`
18
+
19
+ Fetch the poll and all ballots from Nostr, verify every BIP-322 signature, look up UTXO state at the declared snapshot block from mempool.space, and run the pure tally function. Output matches what any conforming implementation produces.
20
+
21
+ ```
22
+ $ oc-vote tally 3054390f047f2703186943a41178bc15931500b5139229517f26e56282026ee5
23
+
24
+ poll: Do we ship?
25
+ poll_id: 3054390f…026ee5
26
+ creator: bc1q…
27
+ mode: public
28
+ weight: sats
29
+ threshold: 100000 sat / 30 d
30
+ deadline: 2026-05-08T00:00:00Z
31
+ snapshot: 900412
32
+ ballots: 47
33
+
34
+ STATE: tallied
35
+ turnout: 47 voters, weight 2,814,300,000
36
+
37
+ Ship it ████████████·················· 1,102,900,000 39.2%
38
+ Hold ████████·············· 812,300,000 28.9%
39
+ …
40
+ ```
41
+
42
+ Flags:
43
+
44
+ - `--relay wss://...` (repeatable) — custom relay set. Defaults to the canonical four.
45
+ - `--mempool-base https://...` — UTXO source. Defaults to `https://mempool.space/api`.
46
+ - `--snapshot <height>` — pin a specific snapshot block (overrides `poll.snapshot_block`).
47
+ - `--no-verify` — skip BIP-322 checks (faster, less safe — use only for sanity probes).
48
+ - `--json` — JSON output instead of the human-readable bar chart.
49
+
50
+ ### `oc-vote verify <poll_id>`
51
+
52
+ Verify every BIP-322 signature on the poll and on each ballot. Prints a short summary; `--json` gives per-ballot details.
53
+
54
+ ### `oc-vote show <poll_id>`
55
+
56
+ Print the poll metadata + ballot count without running the tally. Useful for quickly inspecting a poll.
57
+
58
+ ## Why
59
+
60
+ The web UI at [vote.ochk.io](https://vote.ochk.io) is convenience. The canonical tally is whatever the spec's pure function produces. If the web UI ever disagrees with what this CLI outputs, **the CLI is correct** and the UI has a bug — file an issue with the diff.
61
+
62
+ ## Related
63
+
64
+ - Protocol: [`orangecheck/oc-vote-protocol`](https://github.com/orangecheck/oc-vote-protocol)
65
+ - Reference web client: [`orangecheck/oc-vote-web`](https://github.com/orangecheck/oc-vote-web)
66
+ - Library: [`@orangecheck/vote-core`](https://npmjs.com/package/@orangecheck/vote-core)
67
+
68
+ ## License
69
+
70
+ MIT.
package/dist/cli.js ADDED
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { pollId, ballotId, tally } from '@orangecheck/vote-core';
4
+ import WebSocket from 'ws';
5
+ import { hexDecode, utf8Decode } from '@orangecheck/lock-crypto';
6
+ import { unseal } from '@orangecheck/lock-core';
7
+
8
+ var DEFAULT_RELAYS = [
9
+ "wss://relay.damus.io",
10
+ "wss://relay.nostr.band",
11
+ "wss://nos.lol",
12
+ "wss://relay.snort.social"
13
+ ];
14
+ async function queryRelays(filter, relays = DEFAULT_RELAYS, timeoutMs = 8e3) {
15
+ const byId = /* @__PURE__ */ new Map();
16
+ await Promise.all(
17
+ relays.map(
18
+ (url) => new Promise((resolve) => {
19
+ const subId = "ocvcli-" + Math.random().toString(36).slice(2, 10);
20
+ let settled = false;
21
+ let ws = null;
22
+ const timer = setTimeout(() => {
23
+ if (settled) return;
24
+ settled = true;
25
+ try {
26
+ ws?.close();
27
+ } catch {
28
+ }
29
+ resolve();
30
+ }, timeoutMs);
31
+ try {
32
+ ws = new WebSocket(url);
33
+ ws.on("open", () => ws?.send(JSON.stringify(["REQ", subId, filter])));
34
+ ws.on("message", (raw) => {
35
+ try {
36
+ const arr = JSON.parse(raw.toString());
37
+ if (!Array.isArray(arr) || arr.length === 0) return;
38
+ if (arr[0] === "EVENT" && arr[1] === subId) {
39
+ const event = arr[2];
40
+ if (event?.id) byId.set(event.id, event);
41
+ } else if (arr[0] === "EOSE" && arr[1] === subId) {
42
+ if (settled) return;
43
+ settled = true;
44
+ clearTimeout(timer);
45
+ try {
46
+ ws?.send(JSON.stringify(["CLOSE", subId]));
47
+ ws?.close();
48
+ } catch {
49
+ }
50
+ resolve();
51
+ }
52
+ } catch {
53
+ }
54
+ });
55
+ ws.on("error", () => {
56
+ if (settled) return;
57
+ settled = true;
58
+ clearTimeout(timer);
59
+ resolve();
60
+ });
61
+ ws.on("close", () => {
62
+ if (settled) return;
63
+ settled = true;
64
+ clearTimeout(timer);
65
+ resolve();
66
+ });
67
+ } catch {
68
+ if (!settled) {
69
+ settled = true;
70
+ clearTimeout(timer);
71
+ resolve();
72
+ }
73
+ }
74
+ })
75
+ )
76
+ );
77
+ return Array.from(byId.values()).sort((a, b) => b.created_at - a.created_at);
78
+ }
79
+ async function fetchPollEvent(pollId, relays) {
80
+ const events = await queryRelays(
81
+ { kinds: [30080], "#d": [`oc-vote:poll:${pollId}`], limit: 1 },
82
+ relays
83
+ );
84
+ return events[0] ?? null;
85
+ }
86
+ async function fetchBallotEvents(pollId, relays) {
87
+ return queryRelays({ kinds: [30081], "#poll_id": [pollId] }, relays);
88
+ }
89
+ async function fetchRevealEvent(pollId, relays) {
90
+ const events = await queryRelays(
91
+ { kinds: [30082], "#d": [`oc-vote:reveal:${pollId}`], limit: 1 },
92
+ relays
93
+ );
94
+ return events[0] ?? null;
95
+ }
96
+
97
+ // src/commands/show.ts
98
+ async function runShow(opts) {
99
+ const pid = opts.pollId;
100
+ if (!/^[0-9a-f]{64}$/.test(pid)) {
101
+ throw new Error("poll id must be 64 hex chars");
102
+ }
103
+ const relays = opts.relays ?? DEFAULT_RELAYS;
104
+ const [pollEvent, ballotEvents] = await Promise.all([
105
+ fetchPollEvent(pid, relays),
106
+ fetchBallotEvents(pid, relays)
107
+ ]);
108
+ if (!pollEvent) throw new Error("poll not found");
109
+ const poll = JSON.parse(pollEvent.content);
110
+ if (pollId(poll) !== pid) throw new Error("poll content mismatch");
111
+ if (opts.json) {
112
+ process.stdout.write(
113
+ JSON.stringify({ poll, ballot_count: ballotEvents.length }, null, 2) + "\n"
114
+ );
115
+ return;
116
+ }
117
+ const w = (s) => process.stdout.write(s);
118
+ w(`
119
+ question: ${poll.question}
120
+ `);
121
+ w(` options: ${poll.options.map((o) => `${o.id} (${o.label})`).join(", ")}
122
+ `);
123
+ w(` creator: ${poll.creator}
124
+ `);
125
+ w(` mode: ${poll.mode}
126
+ `);
127
+ w(` weight: ${poll.weight_mode}${poll.weight_mode === "sats_days" && poll.weight_params?.cap_days ? ` (cap ${poll.weight_params.cap_days}d)` : ""}
128
+ `);
129
+ w(` threshold: ${poll.min_sats} sat / ${poll.min_days} d
130
+ `);
131
+ w(` tiebreak: ${poll.tiebreak}
132
+ `);
133
+ w(` deadline: ${poll.deadline}
134
+ `);
135
+ w(` snapshot: ${poll.snapshot_block}
136
+ `);
137
+ w(` ballots: ${ballotEvents.length}
138
+ `);
139
+ if (poll.notes) w(` notes: ${poll.notes}
140
+ `);
141
+ w(`
142
+ `);
143
+ }
144
+
145
+ // src/utxos.ts
146
+ function mempoolSource(base = "https://mempool.space/api") {
147
+ return {
148
+ async fetchUtxos(address) {
149
+ const res = await fetch(`${base}/address/${address}/utxo`, {
150
+ headers: { accept: "application/json" }
151
+ });
152
+ if (!res.ok) throw new Error(`mempool utxo ${res.status}`);
153
+ const data = await res.json();
154
+ return data.filter((u) => u.status.confirmed && typeof u.status.block_height === "number").map((u) => ({
155
+ value: u.value,
156
+ confirmed_height: u.status.block_height
157
+ }));
158
+ },
159
+ async fetchTipHeight() {
160
+ const res = await fetch(`${base}/blocks/tip/height`);
161
+ if (!res.ok) throw new Error(`tip ${res.status}`);
162
+ return Number.parseInt((await res.text()).trim(), 10);
163
+ }
164
+ };
165
+ }
166
+ function buildLookup(source) {
167
+ return async (addr, snapshot) => {
168
+ const all = await source.fetchUtxos(addr);
169
+ return all.filter((u) => u.confirmed_height <= snapshot);
170
+ };
171
+ }
172
+
173
+ // src/commands/tally.ts
174
+ async function runTally(opts) {
175
+ const { pollId: pid } = opts;
176
+ if (!/^[0-9a-f]{64}$/.test(pid)) {
177
+ throw new Error("poll id must be 64 hex chars");
178
+ }
179
+ const relays = opts.relays ?? DEFAULT_RELAYS;
180
+ const [pollEvent, ballotEvents, revealEvent] = await Promise.all([
181
+ fetchPollEvent(pid, relays),
182
+ fetchBallotEvents(pid, relays),
183
+ fetchRevealEvent(pid, relays)
184
+ ]);
185
+ if (!pollEvent) throw new Error("poll not found on any relay");
186
+ const poll = JSON.parse(pollEvent.content);
187
+ if (pollId(poll) !== pid) {
188
+ throw new Error("poll content does not match poll_id");
189
+ }
190
+ const ballots = [];
191
+ const seen = /* @__PURE__ */ new Set();
192
+ for (const ev of ballotEvents) {
193
+ try {
194
+ const b = JSON.parse(ev.content);
195
+ if (b.poll_id !== pid) continue;
196
+ const id = ballotId(b);
197
+ if (seen.has(id)) continue;
198
+ seen.add(id);
199
+ ballots.push(b);
200
+ } catch {
201
+ }
202
+ }
203
+ const reveal = revealEvent ? JSON.parse(revealEvent.content) : null;
204
+ const source = mempoolSource(opts.mempoolBase);
205
+ const utxosAt = buildLookup(source);
206
+ let snapshot = poll.snapshot_block;
207
+ if (typeof snapshot !== "number") {
208
+ snapshot = opts.snapshotBlock ?? await source.fetchTipHeight();
209
+ }
210
+ let revealedOptions;
211
+ if (poll.mode === "secret" && reveal) {
212
+ revealedOptions = {};
213
+ for (const b of ballots) {
214
+ if (!b.secret) continue;
215
+ try {
216
+ const result2 = await unseal({
217
+ envelope: b.secret.envelope,
218
+ device: { device_id: "reveal", secretKey: hexDecode(reveal.reveal_sk) },
219
+ skipSenderVerification: true
220
+ });
221
+ revealedOptions[b.voter] = utf8Decode(result2.payload);
222
+ } catch {
223
+ }
224
+ }
225
+ }
226
+ const verify = opts.verify !== false;
227
+ const verifyBip322 = verify ? async (address, message, signatureB64) => {
228
+ try {
229
+ const mod = await import('bip322-js');
230
+ const Verifier = mod.Verifier ?? mod.default?.Verifier;
231
+ if (!Verifier) return false;
232
+ return Verifier.verifySignature(address, message, signatureB64);
233
+ } catch {
234
+ return false;
235
+ }
236
+ } : void 0;
237
+ const result = await tally({
238
+ poll: { ...poll, snapshot_block: snapshot },
239
+ ballots,
240
+ utxosAt,
241
+ skipSignatures: !verify,
242
+ ...verifyBip322 ? { verifyBip322 } : {},
243
+ ...revealedOptions ? { revealedOptions } : {}
244
+ });
245
+ const output = {
246
+ poll_id: pid,
247
+ question: poll.question,
248
+ creator: poll.creator,
249
+ mode: poll.mode,
250
+ weight_mode: poll.weight_mode,
251
+ deadline: poll.deadline,
252
+ ballot_count: ballots.length,
253
+ reveal_present: reveal != null,
254
+ ...result
255
+ };
256
+ if (opts.json) {
257
+ process.stdout.write(JSON.stringify(output, null, 2) + "\n");
258
+ return;
259
+ }
260
+ const w = (s) => process.stdout.write(s);
261
+ w(`
262
+ poll: ${poll.question}
263
+ `);
264
+ w(` poll_id: ${pid}
265
+ `);
266
+ w(` creator: ${poll.creator}
267
+ `);
268
+ w(` mode: ${poll.mode}${poll.mode === "secret" ? reveal ? " (revealed)" : " (awaiting reveal)" : ""}
269
+ `);
270
+ w(` weight: ${poll.weight_mode}
271
+ `);
272
+ w(` threshold: ${poll.min_sats} sat / ${poll.min_days} d
273
+ `);
274
+ w(` deadline: ${poll.deadline}
275
+ `);
276
+ w(` snapshot: ${snapshot}
277
+ `);
278
+ w(` ballots: ${ballots.length}
279
+ `);
280
+ w(`
281
+ `);
282
+ if (result.state === "awaiting_reveal") {
283
+ w(` STATE: awaiting reveal \u2014 secret-mode poll, creator has not yet published reveal_sk
284
+
285
+ `);
286
+ return;
287
+ }
288
+ w(` STATE: tallied
289
+ `);
290
+ w(` turnout: ${result.turnout.voters} voters, weight ${result.turnout.weight.toLocaleString()}
291
+
292
+ `);
293
+ const total = result.turnout.weight;
294
+ const maxLabel = Math.max(...poll.options.map((o) => o.label.length));
295
+ for (const opt of poll.options) {
296
+ const wgt = result.tallies[opt.id] ?? 0;
297
+ const pct = total > 0 ? wgt / total * 100 : 0;
298
+ const barLen = total > 0 ? Math.round(wgt / total * 30) : 0;
299
+ const bar = "\u2588".repeat(barLen) + "\xB7".repeat(30 - barLen);
300
+ w(` ${opt.label.padEnd(maxLabel)} ${bar} ${wgt.toLocaleString().padStart(12)} ${pct.toFixed(1).padStart(5)}%
301
+ `);
302
+ }
303
+ w(`
304
+ `);
305
+ }
306
+ async function runVerify(opts) {
307
+ const pid = opts.pollId;
308
+ if (!/^[0-9a-f]{64}$/.test(pid)) {
309
+ throw new Error("poll id must be 64 hex chars");
310
+ }
311
+ const relays = opts.relays ?? DEFAULT_RELAYS;
312
+ const [pollEvent, ballotEvents] = await Promise.all([
313
+ fetchPollEvent(pid, relays),
314
+ fetchBallotEvents(pid, relays)
315
+ ]);
316
+ if (!pollEvent) throw new Error("poll not found");
317
+ const poll = JSON.parse(pollEvent.content);
318
+ if (pollId(poll) !== pid) throw new Error("poll content mismatch");
319
+ const mod = await import('bip322-js');
320
+ const Verifier = mod.Verifier ?? mod.default?.Verifier;
321
+ if (!Verifier) throw new Error("bip322 verifier unavailable");
322
+ const pollSigOk = Verifier.verifySignature(poll.creator, pid, poll.sig.value);
323
+ const ballots = [];
324
+ const seen = /* @__PURE__ */ new Set();
325
+ const ballotResults = [];
326
+ for (const ev of ballotEvents) {
327
+ try {
328
+ const b = JSON.parse(ev.content);
329
+ if (b.poll_id !== pid) continue;
330
+ const bid = ballotId(b);
331
+ if (seen.has(bid)) continue;
332
+ seen.add(bid);
333
+ ballots.push(b);
334
+ const ok = Verifier.verifySignature(b.voter, bid, b.sig.value);
335
+ ballotResults.push({ voter: b.voter, ballot_id: bid, ok });
336
+ } catch {
337
+ }
338
+ }
339
+ const okCount = ballotResults.filter((r) => r.ok).length;
340
+ if (opts.json) {
341
+ process.stdout.write(
342
+ JSON.stringify(
343
+ {
344
+ poll_id: pid,
345
+ poll_signature_valid: pollSigOk,
346
+ ballot_count: ballots.length,
347
+ ballots_valid: okCount,
348
+ ballots_invalid: ballots.length - okCount,
349
+ ballot_details: ballotResults
350
+ },
351
+ null,
352
+ 2
353
+ ) + "\n"
354
+ );
355
+ return;
356
+ }
357
+ const w = (s) => process.stdout.write(s);
358
+ w(`
359
+ poll_id: ${pid}
360
+ `);
361
+ w(` poll signature: ${pollSigOk ? "VALID \u2713" : "INVALID \u2717"}
362
+ `);
363
+ w(` ballots observed: ${ballots.length}
364
+ `);
365
+ w(` ballots valid: ${okCount}
366
+ `);
367
+ w(` ballots invalid: ${ballots.length - okCount}
368
+
369
+ `);
370
+ if (ballots.length - okCount > 0) {
371
+ w(` invalid ballots:
372
+ `);
373
+ for (const r of ballotResults.filter((x) => !x.ok)) {
374
+ w(` - ${r.voter} \xB7 ${r.ballot_id}
375
+ `);
376
+ }
377
+ w(`
378
+ `);
379
+ }
380
+ }
381
+
382
+ // src/cli.ts
383
+ var program = new Command();
384
+ program.name("oc-vote").description("OC Vote \u2014 sovereign signaling for the open web").version("0.1.0");
385
+ program.command("tally <poll_id>").description("Fetch the poll + ballots and run the pure tally function").option("--relay <url>", "Nostr relay (repeatable)", (v, prev = []) => [...prev, v], []).option("--mempool-base <url>", "mempool.space-compatible base URL", "https://mempool.space/api").option("--snapshot <height>", "Override snapshot block (integer)").option("--no-verify", "Skip BIP-322 signature verification (faster, less safe)").option("--json", "Emit JSON instead of human-readable output").action(async (pollId, opts) => {
386
+ await runTally({
387
+ pollId,
388
+ relays: opts.relay?.length ? opts.relay : void 0,
389
+ mempoolBase: opts.mempoolBase,
390
+ json: Boolean(opts.json),
391
+ verify: opts.verify !== false,
392
+ snapshotBlock: opts.snapshot ? Number.parseInt(String(opts.snapshot), 10) : void 0
393
+ });
394
+ });
395
+ program.command("verify <poll_id>").description("Verify every BIP-322 signature on the poll + its ballots").option("--relay <url>", "Nostr relay (repeatable)", (v, prev = []) => [...prev, v], []).option("--json", "Emit JSON").action(async (pollId, opts) => {
396
+ await runVerify({
397
+ pollId,
398
+ relays: opts.relay?.length ? opts.relay : void 0,
399
+ json: Boolean(opts.json)
400
+ });
401
+ });
402
+ program.command("show <poll_id>").description("Print the poll metadata + ballot count without running the tally").option("--relay <url>", "Nostr relay (repeatable)", (v, prev = []) => [...prev, v], []).option("--json", "Emit JSON").action(async (pollId, opts) => {
403
+ await runShow({
404
+ pollId,
405
+ relays: opts.relay?.length ? opts.relay : void 0,
406
+ json: Boolean(opts.json)
407
+ });
408
+ });
409
+ program.parseAsync(process.argv).catch((err) => {
410
+ const msg = err instanceof Error ? err.message : String(err);
411
+ process.stderr.write(`oc-vote: ${msg}
412
+ `);
413
+ process.exit(1);
414
+ });
415
+ //# sourceMappingURL=cli.js.map
416
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nostr.ts","../src/commands/show.ts","../src/utxos.ts","../src/commands/tally.ts","../src/commands/verify.ts","../src/cli.ts"],"names":["computePollId","result","computeBallotId"],"mappings":"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","file":"cli.js","sourcesContent":["// Minimal NIP-01 WebSocket client for oc-vote. Node-native via the `ws` package.\n\nimport WebSocket from 'ws';\n\nexport interface NostrEvent {\n id: string;\n kind: number;\n pubkey: string;\n created_at: number;\n content: string;\n tags: string[][];\n sig: string;\n}\n\nexport interface Filter {\n kinds?: number[];\n '#d'?: string[];\n '#poll_id'?: string[];\n limit?: number;\n}\n\nexport const DEFAULT_RELAYS = [\n 'wss://relay.damus.io',\n 'wss://relay.nostr.band',\n 'wss://nos.lol',\n 'wss://relay.snort.social',\n];\n\nexport async function queryRelays(\n filter: Filter,\n relays: string[] = DEFAULT_RELAYS,\n timeoutMs = 8000\n): Promise<NostrEvent[]> {\n const byId = new Map<string, NostrEvent>();\n await Promise.all(\n relays.map(\n (url) =>\n new Promise<void>((resolve) => {\n const subId = 'ocvcli-' + Math.random().toString(36).slice(2, 10);\n let settled = false;\n let ws: WebSocket | null = null;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n try {\n ws?.close();\n } catch {}\n resolve();\n }, timeoutMs);\n try {\n ws = new WebSocket(url);\n ws.on('open', () => ws?.send(JSON.stringify(['REQ', subId, filter])));\n ws.on('message', (raw: WebSocket.RawData) => {\n try {\n const arr = JSON.parse(raw.toString()) as unknown[];\n if (!Array.isArray(arr) || arr.length === 0) return;\n if (arr[0] === 'EVENT' && arr[1] === subId) {\n const event = arr[2] as NostrEvent;\n if (event?.id) byId.set(event.id, event);\n } else if (arr[0] === 'EOSE' && arr[1] === subId) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n try {\n ws?.send(JSON.stringify(['CLOSE', subId]));\n ws?.close();\n } catch {}\n resolve();\n }\n } catch {\n // ignore parse errors\n }\n });\n ws.on('error', () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n });\n ws.on('close', () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n });\n } catch {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve();\n }\n }\n })\n )\n );\n return Array.from(byId.values()).sort((a, b) => b.created_at - a.created_at);\n}\n\nexport async function fetchPollEvent(pollId: string, relays?: string[]) {\n const events = await queryRelays(\n { kinds: [30080], '#d': [`oc-vote:poll:${pollId}`], limit: 1 },\n relays\n );\n return events[0] ?? null;\n}\n\nexport async function fetchBallotEvents(pollId: string, relays?: string[]) {\n return queryRelays({ kinds: [30081], '#poll_id': [pollId] }, relays);\n}\n\nexport async function fetchRevealEvent(pollId: string, relays?: string[]) {\n const events = await queryRelays(\n { kinds: [30082], '#d': [`oc-vote:reveal:${pollId}`], limit: 1 },\n relays\n );\n return events[0] ?? null;\n}\n","// oc-vote show <poll_id> — print the poll metadata + ballot count.\n\nimport { pollId as computePollId, type Poll } from '@orangecheck/vote-core';\n\nimport { DEFAULT_RELAYS, fetchBallotEvents, fetchPollEvent } from '../nostr.js';\n\nexport interface ShowOptions {\n pollId: string;\n relays?: string[];\n json?: boolean;\n}\n\nexport async function runShow(opts: ShowOptions): Promise<void> {\n const pid = opts.pollId;\n if (!/^[0-9a-f]{64}$/.test(pid)) {\n throw new Error('poll id must be 64 hex chars');\n }\n const relays = opts.relays ?? DEFAULT_RELAYS;\n\n const [pollEvent, ballotEvents] = await Promise.all([\n fetchPollEvent(pid, relays),\n fetchBallotEvents(pid, relays),\n ]);\n if (!pollEvent) throw new Error('poll not found');\n const poll = JSON.parse(pollEvent.content) as Poll;\n if (computePollId(poll) !== pid) throw new Error('poll content mismatch');\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify({ poll, ballot_count: ballotEvents.length }, null, 2) + '\\n'\n );\n return;\n }\n\n const w = (s: string) => process.stdout.write(s);\n w(`\\n question: ${poll.question}\\n`);\n w(` options: ${poll.options.map((o) => `${o.id} (${o.label})`).join(', ')}\\n`);\n w(` creator: ${poll.creator}\\n`);\n w(` mode: ${poll.mode}\\n`);\n w(` weight: ${poll.weight_mode}${poll.weight_mode === 'sats_days' && poll.weight_params?.cap_days ? ` (cap ${poll.weight_params.cap_days}d)` : ''}\\n`);\n w(` threshold: ${poll.min_sats} sat / ${poll.min_days} d\\n`);\n w(` tiebreak: ${poll.tiebreak}\\n`);\n w(` deadline: ${poll.deadline}\\n`);\n w(` snapshot: ${poll.snapshot_block}\\n`);\n w(` ballots: ${ballotEvents.length}\\n`);\n if (poll.notes) w(` notes: ${poll.notes}\\n`);\n w(`\\n`);\n}\n","// UTXO snapshot source for the CLI. Defaults to mempool.space; swappable via\n// --mempool-base flag.\n\nimport type { Utxo, UtxoLookup } from '@orangecheck/vote-core';\n\ninterface MempoolUtxo {\n txid: string;\n vout: number;\n value: number;\n status: { confirmed: boolean; block_height?: number; block_time?: number };\n}\n\nexport interface UtxoSource {\n fetchUtxos(address: string): Promise<Utxo[]>;\n fetchTipHeight(): Promise<number>;\n}\n\nexport function mempoolSource(base = 'https://mempool.space/api'): UtxoSource {\n return {\n async fetchUtxos(address) {\n const res = await fetch(`${base}/address/${address}/utxo`, {\n headers: { accept: 'application/json' },\n });\n if (!res.ok) throw new Error(`mempool utxo ${res.status}`);\n const data = (await res.json()) as MempoolUtxo[];\n return data\n .filter((u) => u.status.confirmed && typeof u.status.block_height === 'number')\n .map((u) => ({\n value: u.value,\n confirmed_height: u.status.block_height as number,\n }));\n },\n async fetchTipHeight() {\n const res = await fetch(`${base}/blocks/tip/height`);\n if (!res.ok) throw new Error(`tip ${res.status}`);\n return Number.parseInt((await res.text()).trim(), 10);\n },\n };\n}\n\nexport function buildLookup(source: UtxoSource): UtxoLookup {\n return async (addr: string, snapshot: number): Promise<Utxo[]> => {\n const all = await source.fetchUtxos(addr);\n return all.filter((u) => u.confirmed_height <= snapshot);\n };\n}\n","// oc-vote tally <poll_id>\n\nimport { hexDecode, utf8Decode } from '@orangecheck/lock-crypto';\nimport { unseal } from '@orangecheck/lock-core';\nimport {\n ballotId,\n pollId as computePollId,\n tally,\n type Ballot,\n type Poll,\n type Reveal,\n} from '@orangecheck/vote-core';\n\nimport {\n DEFAULT_RELAYS,\n fetchBallotEvents,\n fetchPollEvent,\n fetchRevealEvent,\n} from '../nostr.js';\nimport { buildLookup, mempoolSource } from '../utxos.js';\n\nexport interface TallyOptions {\n pollId: string;\n relays?: string[];\n mempoolBase?: string;\n json?: boolean;\n verify?: boolean;\n snapshotBlock?: number;\n}\n\nexport async function runTally(opts: TallyOptions): Promise<void> {\n const { pollId: pid } = opts;\n if (!/^[0-9a-f]{64}$/.test(pid)) {\n throw new Error('poll id must be 64 hex chars');\n }\n const relays = opts.relays ?? DEFAULT_RELAYS;\n\n const [pollEvent, ballotEvents, revealEvent] = await Promise.all([\n fetchPollEvent(pid, relays),\n fetchBallotEvents(pid, relays),\n fetchRevealEvent(pid, relays),\n ]);\n\n if (!pollEvent) throw new Error('poll not found on any relay');\n const poll = JSON.parse(pollEvent.content) as Poll;\n if (computePollId(poll) !== pid) {\n throw new Error('poll content does not match poll_id');\n }\n\n const ballots: Ballot[] = [];\n const seen = new Set<string>();\n for (const ev of ballotEvents) {\n try {\n const b = JSON.parse(ev.content) as Ballot;\n if (b.poll_id !== pid) continue;\n const id = ballotId(b);\n if (seen.has(id)) continue;\n seen.add(id);\n ballots.push(b);\n } catch {\n // skip malformed\n }\n }\n\n const reveal: Reveal | null = revealEvent\n ? (JSON.parse(revealEvent.content) as Reveal)\n : null;\n\n // Resolve snapshot\n const source = mempoolSource(opts.mempoolBase);\n const utxosAt = buildLookup(source);\n let snapshot = poll.snapshot_block;\n if (typeof snapshot !== 'number') {\n snapshot = opts.snapshotBlock ?? (await source.fetchTipHeight());\n }\n\n // Unseal secret-mode ballots if a reveal is published\n let revealedOptions: Record<string, string> | undefined;\n if (poll.mode === 'secret' && reveal) {\n revealedOptions = {};\n for (const b of ballots) {\n if (!b.secret) continue;\n try {\n const result = await unseal({\n envelope: b.secret.envelope as unknown as Parameters<typeof unseal>[0]['envelope'],\n device: { device_id: 'reveal', secretKey: hexDecode(reveal.reveal_sk) },\n skipSenderVerification: true,\n });\n revealedOptions[b.voter] = utf8Decode(result.payload);\n } catch {\n // skip: bad envelope or wrong reveal key\n }\n }\n }\n\n const verify = opts.verify !== false;\n const verifyBip322 = verify\n ? async (address: string, message: string, signatureB64: string) => {\n try {\n const mod = (await import('bip322-js')) as unknown as {\n Verifier?: { verifySignature(a: string, m: string, s: string): boolean };\n default?: {\n Verifier?: {\n verifySignature(a: string, m: string, s: string): boolean;\n };\n };\n };\n const Verifier = mod.Verifier ?? mod.default?.Verifier;\n if (!Verifier) return false;\n return Verifier.verifySignature(address, message, signatureB64);\n } catch {\n return false;\n }\n }\n : undefined;\n\n const result = await tally({\n poll: { ...poll, snapshot_block: snapshot },\n ballots,\n utxosAt,\n skipSignatures: !verify,\n ...(verifyBip322 ? { verifyBip322 } : {}),\n ...(revealedOptions ? { revealedOptions } : {}),\n });\n\n const output = {\n poll_id: pid,\n question: poll.question,\n creator: poll.creator,\n mode: poll.mode,\n weight_mode: poll.weight_mode,\n deadline: poll.deadline,\n ballot_count: ballots.length,\n reveal_present: reveal != null,\n ...result,\n };\n\n if (opts.json) {\n process.stdout.write(JSON.stringify(output, null, 2) + '\\n');\n return;\n }\n\n // Human-readable\n const w = (s: string) => process.stdout.write(s);\n w(`\\n poll: ${poll.question}\\n`);\n w(` poll_id: ${pid}\\n`);\n w(` creator: ${poll.creator}\\n`);\n w(` mode: ${poll.mode}${poll.mode === 'secret' ? (reveal ? ' (revealed)' : ' (awaiting reveal)') : ''}\\n`);\n w(` weight: ${poll.weight_mode}\\n`);\n w(` threshold: ${poll.min_sats} sat / ${poll.min_days} d\\n`);\n w(` deadline: ${poll.deadline}\\n`);\n w(` snapshot: ${snapshot}\\n`);\n w(` ballots: ${ballots.length}\\n`);\n w(`\\n`);\n\n if (result.state === 'awaiting_reveal') {\n w(` STATE: awaiting reveal — secret-mode poll, creator has not yet published reveal_sk\\n\\n`);\n return;\n }\n\n w(` STATE: tallied\\n`);\n w(` turnout: ${result.turnout.voters} voters, weight ${result.turnout.weight.toLocaleString()}\\n\\n`);\n\n // Sort options by poll.options order\n const total = result.turnout.weight;\n const maxLabel = Math.max(...poll.options.map((o) => o.label.length));\n for (const opt of poll.options) {\n const wgt = result.tallies[opt.id] ?? 0;\n const pct = total > 0 ? (wgt / total) * 100 : 0;\n const barLen = total > 0 ? Math.round((wgt / total) * 30) : 0;\n const bar = '█'.repeat(barLen) + '·'.repeat(30 - barLen);\n w(` ${opt.label.padEnd(maxLabel)} ${bar} ${wgt.toLocaleString().padStart(12)} ${pct.toFixed(1).padStart(5)}%\\n`);\n }\n w(`\\n`);\n}\n","// oc-vote verify <poll_id> — fetch poll + ballots, verify every BIP-322 signature, report.\n\nimport {\n ballotId as computeBallotId,\n pollId as computePollId,\n type Ballot,\n type Poll,\n} from '@orangecheck/vote-core';\n\nimport { DEFAULT_RELAYS, fetchBallotEvents, fetchPollEvent } from '../nostr.js';\n\nexport interface VerifyOptions {\n pollId: string;\n relays?: string[];\n json?: boolean;\n}\n\nexport async function runVerify(opts: VerifyOptions): Promise<void> {\n const pid = opts.pollId;\n if (!/^[0-9a-f]{64}$/.test(pid)) {\n throw new Error('poll id must be 64 hex chars');\n }\n const relays = opts.relays ?? DEFAULT_RELAYS;\n\n const [pollEvent, ballotEvents] = await Promise.all([\n fetchPollEvent(pid, relays),\n fetchBallotEvents(pid, relays),\n ]);\n if (!pollEvent) throw new Error('poll not found');\n const poll = JSON.parse(pollEvent.content) as Poll;\n if (computePollId(poll) !== pid) throw new Error('poll content mismatch');\n\n const mod = (await import('bip322-js')) as unknown as {\n Verifier?: { verifySignature(a: string, m: string, s: string): boolean };\n default?: {\n Verifier?: { verifySignature(a: string, m: string, s: string): boolean };\n };\n };\n const Verifier = mod.Verifier ?? mod.default?.Verifier;\n if (!Verifier) throw new Error('bip322 verifier unavailable');\n\n const pollSigOk = Verifier.verifySignature(poll.creator, pid, poll.sig.value);\n\n const ballots: Ballot[] = [];\n const seen = new Set<string>();\n const ballotResults: Array<{ voter: string; ballot_id: string; ok: boolean }> = [];\n for (const ev of ballotEvents) {\n try {\n const b = JSON.parse(ev.content) as Ballot;\n if (b.poll_id !== pid) continue;\n const bid = computeBallotId(b);\n if (seen.has(bid)) continue;\n seen.add(bid);\n ballots.push(b);\n const ok = Verifier.verifySignature(b.voter, bid, b.sig.value);\n ballotResults.push({ voter: b.voter, ballot_id: bid, ok });\n } catch {\n // skip\n }\n }\n\n const okCount = ballotResults.filter((r) => r.ok).length;\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n poll_id: pid,\n poll_signature_valid: pollSigOk,\n ballot_count: ballots.length,\n ballots_valid: okCount,\n ballots_invalid: ballots.length - okCount,\n ballot_details: ballotResults,\n },\n null,\n 2\n ) + '\\n'\n );\n return;\n }\n\n const w = (s: string) => process.stdout.write(s);\n w(`\\n poll_id: ${pid}\\n`);\n w(` poll signature: ${pollSigOk ? 'VALID ✓' : 'INVALID ✗'}\\n`);\n w(` ballots observed: ${ballots.length}\\n`);\n w(` ballots valid: ${okCount}\\n`);\n w(` ballots invalid: ${ballots.length - okCount}\\n\\n`);\n if (ballots.length - okCount > 0) {\n w(` invalid ballots:\\n`);\n for (const r of ballotResults.filter((x) => !x.ok)) {\n w(` - ${r.voter} · ${r.ballot_id}\\n`);\n }\n w(`\\n`);\n }\n}\n","/**\n * @orangecheck/vote-cli — shell interface to OC Vote.\n *\n * oc-vote tally <poll_id> — run the tally function locally\n * oc-vote verify <poll_id> — verify every BIP-322 signature\n * oc-vote show <poll_id> — print poll metadata + ballot count\n *\n * Defaults to the canonical relay set and mempool.space for UTXO snapshots.\n * Swap via --relay wss://... (repeatable) and --mempool-base https://....\n *\n * Exits non-zero if the tally is disputed (--strict-verify fails) or\n * if the poll cannot be found.\n */\n\nimport { Command } from 'commander';\n\nimport { runShow } from './commands/show.js';\nimport { runTally } from './commands/tally.js';\nimport { runVerify } from './commands/verify.js';\n\nconst program = new Command();\n\nprogram\n .name('oc-vote')\n .description('OC Vote — sovereign signaling for the open web')\n .version('0.1.0');\n\nprogram\n .command('tally <poll_id>')\n .description('Fetch the poll + ballots and run the pure tally function')\n .option('--relay <url>', 'Nostr relay (repeatable)', (v, prev: string[] = []) => [...prev, v], [] as string[])\n .option('--mempool-base <url>', 'mempool.space-compatible base URL', 'https://mempool.space/api')\n .option('--snapshot <height>', 'Override snapshot block (integer)')\n .option('--no-verify', 'Skip BIP-322 signature verification (faster, less safe)')\n .option('--json', 'Emit JSON instead of human-readable output')\n .action(async (pollId: string, opts: Record<string, unknown>) => {\n await runTally({\n pollId,\n relays: (opts.relay as string[] | undefined)?.length ? (opts.relay as string[]) : undefined,\n mempoolBase: opts.mempoolBase as string,\n json: Boolean(opts.json),\n verify: opts.verify !== false,\n snapshotBlock: opts.snapshot ? Number.parseInt(String(opts.snapshot), 10) : undefined,\n });\n });\n\nprogram\n .command('verify <poll_id>')\n .description('Verify every BIP-322 signature on the poll + its ballots')\n .option('--relay <url>', 'Nostr relay (repeatable)', (v, prev: string[] = []) => [...prev, v], [] as string[])\n .option('--json', 'Emit JSON')\n .action(async (pollId: string, opts: Record<string, unknown>) => {\n await runVerify({\n pollId,\n relays: (opts.relay as string[] | undefined)?.length ? (opts.relay as string[]) : undefined,\n json: Boolean(opts.json),\n });\n });\n\nprogram\n .command('show <poll_id>')\n .description('Print the poll metadata + ballot count without running the tally')\n .option('--relay <url>', 'Nostr relay (repeatable)', (v, prev: string[] = []) => [...prev, v], [] as string[])\n .option('--json', 'Emit JSON')\n .action(async (pollId: string, opts: Record<string, unknown>) => {\n await runShow({\n pollId,\n relays: (opts.relay as string[] | undefined)?.length ? (opts.relay as string[]) : undefined,\n json: Boolean(opts.json),\n });\n });\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`oc-vote: ${msg}\\n`);\n process.exit(1);\n});\n"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@orangecheck/vote-cli",
3
+ "version": "0.1.0",
4
+ "description": "Command-line tool for OC Vote — tally, verify, and inspect polls from your shell.",
5
+ "keywords": [
6
+ "oc-vote",
7
+ "orangecheck",
8
+ "bitcoin",
9
+ "voting",
10
+ "bip322",
11
+ "cli",
12
+ "nostr"
13
+ ],
14
+ "author": "OrangeCheck",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/orangecheck/oc-packages.git",
19
+ "directory": "vote-cli"
20
+ },
21
+ "homepage": "https://vote.ochk.io",
22
+ "bugs": {
23
+ "url": "https://github.com/orangecheck/oc-vote-protocol/issues"
24
+ },
25
+ "type": "module",
26
+ "bin": {
27
+ "oc-vote": "./dist/cli.js"
28
+ },
29
+ "main": "./dist/cli.js",
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "type-check": "tsc --noEmit",
39
+ "clean": "rm -rf dist",
40
+ "prepublishOnly": "npm run clean && npm run build"
41
+ },
42
+ "dependencies": {
43
+ "@orangecheck/vote-core": "^0.1.0",
44
+ "@orangecheck/lock-core": "^0.1.0",
45
+ "@orangecheck/lock-crypto": "^0.1.0",
46
+ "bip322-js": "^3.0.0",
47
+ "commander": "^12.1.0",
48
+ "ws": "^8.18.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.10.2",
52
+ "@types/ws": "^8.5.12",
53
+ "tsup": "^8.3.5",
54
+ "typescript": "^5.7.2"
55
+ }
56
+ }