pandora-cli-skills 1.1.53 → 1.1.54

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/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: pandora-cli-skills
3
3
  summary: Canonical skill and operator guide for Pandora CLI including mirror, polymarket, resolve, and LP flows.
4
- version: 1.1.53
4
+ version: 1.1.54
5
5
  ---
6
6
 
7
7
  # Pandora CLI & Skills
@@ -153,6 +153,32 @@ const POLL_STATUS_READ_CANDIDATES = [
153
153
  ];
154
154
 
155
155
  const POLL_FINALIZED_READ_CANDIDATES = [
156
+ {
157
+ fn: 'getFinalizedStatus',
158
+ kind: 'status-answer-epoch',
159
+ abi: [
160
+ {
161
+ type: 'function',
162
+ name: 'getFinalizedStatus',
163
+ stateMutability: 'view',
164
+ inputs: [],
165
+ outputs: [{ type: 'uint8' }, { type: 'uint8' }, { type: 'uint32' }],
166
+ },
167
+ ],
168
+ },
169
+ {
170
+ fn: 'getFinalizedStatus',
171
+ kind: 'bool-answer-epoch',
172
+ abi: [
173
+ {
174
+ type: 'function',
175
+ name: 'getFinalizedStatus',
176
+ stateMutability: 'view',
177
+ inputs: [],
178
+ outputs: [{ type: 'bool' }, { type: 'uint8' }, { type: 'uint32' }],
179
+ },
180
+ ],
181
+ },
156
182
  { fn: 'getFinalizedStatus', abi: [{ type: 'function', name: 'getFinalizedStatus', stateMutability: 'view', inputs: [], outputs: [{ type: 'bool' }] }] },
157
183
  { fn: 'isFinalized', abi: [{ type: 'function', name: 'isFinalized', stateMutability: 'view', inputs: [], outputs: [{ type: 'bool' }] }] },
158
184
  { fn: 'finalized', abi: [{ type: 'function', name: 'finalized', stateMutability: 'view', inputs: [], outputs: [{ type: 'bool' }] }] },
@@ -179,6 +205,28 @@ const POLL_FINALIZATION_EPOCH_READ_CANDIDATES = [
179
205
  },
180
206
  ];
181
207
 
208
+ const POLL_CURRENT_EPOCH_READ_CANDIDATES = [
209
+ {
210
+ fn: 'getCurrentEpoch',
211
+ abi: [{ type: 'function', name: 'getCurrentEpoch', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] }],
212
+ },
213
+ {
214
+ fn: 'currentEpoch',
215
+ abi: [{ type: 'function', name: 'currentEpoch', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] }],
216
+ },
217
+ ];
218
+
219
+ const POLL_EPOCH_LENGTH_READ_CANDIDATES = [
220
+ {
221
+ fn: 'getEpochLength',
222
+ abi: [{ type: 'function', name: 'getEpochLength', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] }],
223
+ },
224
+ {
225
+ fn: 'epochLength',
226
+ abi: [{ type: 'function', name: 'epochLength', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] }],
227
+ },
228
+ ];
229
+
182
230
  const POLL_OPERATOR_READ_CANDIDATES = [
183
231
  { fn: 'arbiter', abi: [{ type: 'function', name: 'arbiter', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] }] },
184
232
  { fn: 'operator', abi: [{ type: 'function', name: 'operator', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] }] },
@@ -380,12 +428,12 @@ async function tryReadContractAny(publicClient, address, candidates = []) {
380
428
  functionName: candidate.fn,
381
429
  args: [],
382
430
  });
383
- return { ok: true, fn: candidate.fn, value };
431
+ return { ok: true, fn: candidate.fn, value, candidate };
384
432
  } catch {
385
433
  // continue
386
434
  }
387
435
  }
388
- return { ok: false, fn: null, value: null };
436
+ return { ok: false, fn: null, value: null, candidate: null };
389
437
  }
390
438
 
391
439
  function normalizeOptionalBigInt(value) {
@@ -415,6 +463,14 @@ function normalizePollAnswer(value) {
415
463
  return String(asBig);
416
464
  }
417
465
 
466
+ function deriveCurrentEpochFromTimestamp(timestamp, epochLengthSeconds = 300n) {
467
+ const ts = normalizeOptionalBigInt(timestamp);
468
+ if (ts === null) return null;
469
+ const epochLength = normalizeOptionalBigInt(epochLengthSeconds);
470
+ if (epochLength === null || epochLength <= 0n) return null;
471
+ return ts / epochLength;
472
+ }
473
+
418
474
  async function readPollResolutionState(publicClient, pollAddress) {
419
475
  const statusRead = await tryReadContractAny(publicClient, pollAddress, POLL_STATUS_READ_CANDIDATES);
420
476
  const finalizedRead = await tryReadContractAny(publicClient, pollAddress, POLL_FINALIZED_READ_CANDIDATES);
@@ -425,14 +481,50 @@ async function readPollResolutionState(publicClient, pollAddress) {
425
481
  POLL_FINALIZATION_EPOCH_READ_CANDIDATES,
426
482
  );
427
483
  const operatorRead = await tryReadContractAny(publicClient, pollAddress, POLL_OPERATOR_READ_CANDIDATES);
428
- const currentEpoch = await publicClient.getBlockNumber();
484
+ const currentEpochRead = await tryReadContractAny(publicClient, pollAddress, POLL_CURRENT_EPOCH_READ_CANDIDATES);
485
+ const epochLengthRead = await tryReadContractAny(publicClient, pollAddress, POLL_EPOCH_LENGTH_READ_CANDIDATES);
486
+
487
+ let currentEpoch = currentEpochRead.ok ? normalizeOptionalBigInt(currentEpochRead.value) : null;
488
+ if (currentEpoch === null) {
489
+ try {
490
+ const block = await publicClient.getBlock({ blockTag: 'latest' });
491
+ const epochLength = epochLengthRead.ok ? normalizeOptionalBigInt(epochLengthRead.value) : 300n;
492
+ currentEpoch = deriveCurrentEpochFromTimestamp(block && block.timestamp, epochLength === null ? 300n : epochLength);
493
+ } catch {
494
+ currentEpoch = null;
495
+ }
496
+ }
429
497
 
430
498
  const statusNumeric = statusRead.ok ? Number(statusRead.value) : null;
431
- const finalizedBool = finalizedRead.ok ? Boolean(finalizedRead.value) : null;
432
- const answer = answerRead.ok ? normalizePollAnswer(answerRead.value) : null;
433
- const finalizationEpoch = finalizationEpochRead.ok ? normalizeOptionalBigInt(finalizationEpochRead.value) : null;
499
+ let finalizedBool = null;
500
+ let finalizedAnswer = null;
501
+ let finalizedEpoch = null;
502
+ if (finalizedRead.ok) {
503
+ const kind = finalizedRead.candidate && finalizedRead.candidate.kind ? finalizedRead.candidate.kind : null;
504
+ if (kind === 'status-answer-epoch' || kind === 'bool-answer-epoch') {
505
+ const tuple = Array.isArray(finalizedRead.value) ? finalizedRead.value : null;
506
+ if (tuple && tuple.length >= 3) {
507
+ if (kind === 'status-answer-epoch') {
508
+ const statusRaw = normalizeOptionalBigInt(tuple[0]);
509
+ finalizedBool = statusRaw === null ? null : statusRaw >= 2n;
510
+ } else {
511
+ finalizedBool = Boolean(tuple[0]);
512
+ }
513
+ finalizedAnswer = normalizePollAnswer(tuple[1]);
514
+ finalizedEpoch = normalizeOptionalBigInt(tuple[2]);
515
+ }
516
+ } else {
517
+ finalizedBool = Boolean(finalizedRead.value);
518
+ }
519
+ }
520
+ const answer = answerRead.ok ? normalizePollAnswer(answerRead.value) : finalizedAnswer;
521
+ const finalizationEpoch = finalizationEpochRead.ok ? normalizeOptionalBigInt(finalizationEpochRead.value) : finalizedEpoch;
434
522
  const epochsUntilFinalization =
435
- finalizationEpoch === null ? null : finalizationEpoch > currentEpoch ? Number(finalizationEpoch - currentEpoch) : 0;
523
+ (finalizationEpoch === null || currentEpoch === null)
524
+ ? null
525
+ : finalizationEpoch > currentEpoch
526
+ ? Number(finalizationEpoch - currentEpoch)
527
+ : 0;
436
528
  const claimable =
437
529
  answer !== null &&
438
530
  ((finalizedBool === true) || (epochsUntilFinalization !== null && epochsUntilFinalization <= 0));
@@ -443,7 +535,7 @@ async function readPollResolutionState(publicClient, pollAddress) {
443
535
  pollFinalized: finalizedBool,
444
536
  pollAnswer: answer,
445
537
  finalizationEpoch: finalizationEpoch === null ? null : finalizationEpoch.toString(),
446
- currentEpoch: currentEpoch.toString(),
538
+ currentEpoch: currentEpoch === null ? null : currentEpoch.toString(),
447
539
  epochsUntilFinalization,
448
540
  claimable,
449
541
  operator: operatorRead.ok ? normalizeOptionalAddress(operatorRead.value) : null,
@@ -452,6 +544,8 @@ async function readPollResolutionState(publicClient, pollAddress) {
452
544
  finalized: finalizedRead.fn,
453
545
  answer: answerRead.fn,
454
546
  finalizationEpoch: finalizationEpochRead.fn,
547
+ currentEpoch: currentEpochRead.fn,
548
+ epochLength: epochLengthRead.fn,
455
549
  operator: operatorRead.fn,
456
550
  },
457
551
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pandora-cli-skills",
3
- "version": "1.1.53",
3
+ "version": "1.1.54",
4
4
  "description": "Pandora CLI & Skills",
5
5
  "main": "cli/pandora.cjs",
6
6
  "bin": {
@@ -0,0 +1,71 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+
4
+ const { readPollResolutionState } = require('../../cli/lib/market_admin_service.cjs');
5
+
6
+ const POLL_ADDRESS = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
7
+ const OPERATOR = '0x0D7B957C47Da86c2968dc52111D633D42cb7a5F7';
8
+
9
+ test('readPollResolutionState extracts answer/finalization epoch from getFinalizedStatus tuple', async () => {
10
+ const publicClient = {
11
+ async readContract({ functionName }) {
12
+ if (functionName === 'getStatus') return 2n;
13
+ if (functionName === 'getFinalizedStatus') return [2n, 1n, 5908608n];
14
+ if (functionName === 'arbiter') return OPERATOR;
15
+ if (functionName === 'getCurrentEpoch' || functionName === 'getFinalizationEpoch' || functionName === 'answer') {
16
+ throw new Error(`missing function: ${functionName}`);
17
+ }
18
+ if (functionName === 'getEpochLength') return 300n;
19
+ throw new Error(`unexpected function read: ${functionName}`);
20
+ },
21
+ async getBlock() {
22
+ return { timestamp: 1772550000n }; // 5908500 * 300
23
+ },
24
+ };
25
+
26
+ const state = await readPollResolutionState(publicClient, POLL_ADDRESS);
27
+
28
+ assert.equal(state.pollAddress, POLL_ADDRESS);
29
+ assert.equal(state.marketState, 2);
30
+ assert.equal(state.pollFinalized, true);
31
+ assert.equal(state.pollAnswer, 'yes');
32
+ assert.equal(state.finalizationEpoch, '5908608');
33
+ assert.equal(state.currentEpoch, '5908500');
34
+ assert.equal(state.epochsUntilFinalization, 108);
35
+ assert.equal(state.claimable, true);
36
+ assert.equal(state.operator, OPERATOR.toLowerCase());
37
+ assert.equal(state.readSources.finalized, 'getFinalizedStatus');
38
+ assert.equal(state.readSources.answer, null);
39
+ });
40
+
41
+ test('readPollResolutionState uses dedicated answer/currentEpoch readers when available', async () => {
42
+ const publicClient = {
43
+ async readContract({ functionName }) {
44
+ if (functionName === 'getStatus') return 1n;
45
+ if (functionName === 'getFinalizedStatus') throw new Error('tuple reader not supported');
46
+ if (functionName === 'isFinalized') return false;
47
+ if (functionName === 'answer') return 0n;
48
+ if (functionName === 'getFinalizationEpoch') return 5908610n;
49
+ if (functionName === 'getCurrentEpoch') return 5908600n;
50
+ if (functionName === 'arbiter') return OPERATOR;
51
+ if (functionName === 'getEpochLength') throw new Error('epoch length not needed');
52
+ throw new Error(`unexpected function read: ${functionName}`);
53
+ },
54
+ async getBlock() {
55
+ throw new Error('getBlock should not be called when getCurrentEpoch is available');
56
+ },
57
+ };
58
+
59
+ const state = await readPollResolutionState(publicClient, POLL_ADDRESS);
60
+
61
+ assert.equal(state.pollFinalized, false);
62
+ assert.equal(state.pollAnswer, 'no');
63
+ assert.equal(state.finalizationEpoch, '5908610');
64
+ assert.equal(state.currentEpoch, '5908600');
65
+ assert.equal(state.epochsUntilFinalization, 10);
66
+ assert.equal(state.claimable, false);
67
+ assert.equal(state.readSources.finalized, 'isFinalized');
68
+ assert.equal(state.readSources.answer, 'answer');
69
+ assert.equal(state.readSources.currentEpoch, 'getCurrentEpoch');
70
+ });
71
+