pandora-cli-skills 1.1.13 → 1.1.15

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.
@@ -65,7 +65,7 @@ const PREDICTION_AMM_ABI = [
65
65
  { name: 'minCollateralOut', type: 'uint256' },
66
66
  { name: 'deadline', type: 'uint256' },
67
67
  ],
68
- outputs: [{ type: 'uint256' }],
68
+ outputs: [],
69
69
  },
70
70
  ];
71
71
 
@@ -115,13 +115,18 @@ async function buildMirrorPlan(options = {}) {
115
115
  const distribution = computeDistributionHint(sourceYesProbability === null ? 0.5 : sourceYesProbability);
116
116
  const rules = buildRuleTemplate(sourceMarket);
117
117
 
118
- const match = await findBestPandoraMatch({
119
- indexerUrl: options.indexerUrl,
120
- timeoutMs: options.timeoutMs,
121
- chainId: options.chainId,
122
- sourceQuestion: sourceMarket.question,
123
- limit: 150,
124
- });
118
+ let match = { best: null, diagnostics: [] };
119
+ try {
120
+ match = await findBestPandoraMatch({
121
+ indexerUrl: options.indexerUrl,
122
+ timeoutMs: options.timeoutMs,
123
+ chainId: options.chainId,
124
+ sourceQuestion: sourceMarket.question,
125
+ limit: 150,
126
+ });
127
+ } catch (err) {
128
+ diagnostics.push(`Duplicate-check fallback: ${err && err.message ? err.message : String(err)}`);
129
+ }
125
130
 
126
131
  if (Number.isFinite(sourceMarket.closeTimestamp)) {
127
132
  const nowSec = Math.floor(Date.now() / 1000);
@@ -405,20 +410,24 @@ async function browseMirrorMarkets(options = {}) {
405
410
  for (const entry of polymarket.items || []) {
406
411
  let existingMirror = null;
407
412
  if (options.indexerUrl) {
408
- const match = await findBestPandoraMatch({
409
- indexerUrl: options.indexerUrl,
410
- timeoutMs: options.timeoutMs,
411
- chainId: options.chainId,
412
- sourceQuestion: entry.question,
413
- limit: 100,
414
- });
415
- if (match.best && match.best.similarity && Number(match.best.similarity.score) >= 0.86) {
416
- existingMirror = {
417
- marketAddress: match.best.marketAddress,
418
- similarity: match.best.similarity.score,
419
- };
413
+ try {
414
+ const match = await findBestPandoraMatch({
415
+ indexerUrl: options.indexerUrl,
416
+ timeoutMs: options.timeoutMs,
417
+ chainId: options.chainId,
418
+ sourceQuestion: entry.question,
419
+ limit: 100,
420
+ });
421
+ if (match.best && match.best.similarity && Number(match.best.similarity.score) >= 0.86) {
422
+ existingMirror = {
423
+ marketAddress: match.best.marketAddress,
424
+ similarity: match.best.similarity.score,
425
+ };
426
+ }
427
+ diagnostics.push(...(match.diagnostics || []));
428
+ } catch (err) {
429
+ diagnostics.push(`Duplicate-check skipped for "${entry.slug || entry.marketId || entry.question || 'market'}": ${err && err.message ? err.message : String(err)}`);
420
430
  }
421
- diagnostics.push(...(match.diagnostics || []));
422
431
  }
423
432
 
424
433
  items.push({
@@ -133,7 +133,8 @@ function evaluateStrictGates(context) {
133
133
  );
134
134
 
135
135
  const depthRequired = toNumber(context.plannedHedgeUsdc) || 0;
136
- const depthAvailable = toNumber(context.depthWithinSlippageUsd) || 0;
136
+ const explicitHedgeDepth = toNumber(context.hedgeDepthWithinSlippageUsd);
137
+ const depthAvailable = explicitHedgeDepth === null ? toNumber(context.depthWithinSlippageUsd) || 0 : explicitHedgeDepth;
137
138
  add(
138
139
  'DEPTH_COVERAGE',
139
140
  depthRequired <= 0 ? true : depthAvailable >= depthRequired,
@@ -209,6 +210,7 @@ async function runMirrorSync(options, deps = {}) {
209
210
  hedgeEnabled: options.hedgeEnabled,
210
211
  hedgeRatio: options.hedgeRatio,
211
212
  hedgeTriggerUsdc: options.hedgeTriggerUsdc,
213
+ forceGate: Boolean(options.forceGate),
212
214
  };
213
215
 
214
216
  const hash = strategyHash(strategy);
@@ -354,6 +356,12 @@ async function runMirrorSync(options, deps = {}) {
354
356
  minTimeToExpirySec: snapshotMetrics.minTimeToExpirySec,
355
357
  minimumTimeToCloseSec,
356
358
  depthWithinSlippageUsd: depth.depthWithinSlippageUsd,
359
+ hedgeDepthWithinSlippageUsd:
360
+ plannedHedgeUsdc > 0
361
+ ? gapUsdc >= 0
362
+ ? depth.yesDepth && depth.yesDepth.depthUsd
363
+ : depth.noDepth && depth.noDepth.depthUsd
364
+ : null,
357
365
  depthSlippageBps: options.depthSlippageBps,
358
366
  maxOpenExposureUsdc: options.maxOpenExposureUsdc,
359
367
  maxTradesPerDay: options.maxTradesPerDay,
@@ -380,6 +388,16 @@ async function runMirrorSync(options, deps = {}) {
380
388
  plannedRebalanceUsdc,
381
389
  plannedSpendUsdc,
382
390
  depthWithinSlippageUsd: depth.depthWithinSlippageUsd,
391
+ hedgeDepthWithinSlippageUsd:
392
+ plannedHedgeUsdc > 0
393
+ ? gapUsdc >= 0
394
+ ? toNumber(depth.yesDepth && depth.yesDepth.depthUsd) || 0
395
+ : toNumber(depth.noDepth && depth.noDepth.depthUsd) || 0
396
+ : null,
397
+ yesDepthWithinSlippageUsd: toNumber(depth.yesDepth && depth.yesDepth.depthUsd),
398
+ noDepthWithinSlippageUsd: toNumber(depth.noDepth && depth.noDepth.depthUsd),
399
+ minDepthWithinSlippageUsd: toNumber(depth.minDepthWithinSlippageUsd),
400
+ bestDepthWithinSlippageUsd: toNumber(depth.bestDepthWithinSlippageUsd),
383
401
  },
384
402
  actionPlan: {
385
403
  rebalanceSide: plannedRebalanceUsdc > 0 ? rebalanceSide : null,
@@ -400,7 +418,7 @@ async function runMirrorSync(options, deps = {}) {
400
418
  reason: 'Duplicate trigger bucket (idempotency key already processed).',
401
419
  idempotencyKey,
402
420
  };
403
- } else if (!gate.ok) {
421
+ } else if (!gate.ok && !options.forceGate) {
404
422
  snapshot.action = {
405
423
  mode: options.executeLive ? 'live' : 'paper',
406
424
  status: 'blocked',
@@ -413,6 +431,8 @@ async function runMirrorSync(options, deps = {}) {
413
431
  mode: options.executeLive ? 'live' : 'paper',
414
432
  status: options.executeLive ? 'executed' : 'simulated',
415
433
  idempotencyKey,
434
+ forcedGateBypass: Boolean(!gate.ok && options.forceGate),
435
+ failedChecks: !gate.ok ? gate.failedChecks : [],
416
436
  rebalance: null,
417
437
  hedge: null,
418
438
  };
@@ -578,6 +598,7 @@ async function runMirrorSync(options, deps = {}) {
578
598
  polymarketMarketId: options.polymarketMarketId,
579
599
  polymarketSlug: options.polymarketSlug,
580
600
  trustDeploy: Boolean(options.trustDeploy),
601
+ forceGate: Boolean(options.forceGate),
581
602
  intervalMs: options.intervalMs,
582
603
  minimumTimeToCloseSec,
583
604
  driftTriggerBps: options.driftTriggerBps,
@@ -3,6 +3,7 @@ const { createIndexerClient } = require('./indexer_client.cjs');
3
3
  const { resolvePolymarketMarket } = require('./polymarket_trade_adapter.cjs');
4
4
 
5
5
  const MIRROR_VERIFY_SCHEMA_VERSION = '1.0.0';
6
+ const USDC_DECIMALS = 6;
6
7
 
7
8
  function toNumber(value) {
8
9
  const numeric = Number(value);
@@ -16,6 +17,12 @@ function round(value, decimals = 6) {
16
17
  return Math.round(value * factor) / factor;
17
18
  }
18
19
 
20
+ function normalizeUsdcRawToUsd(value) {
21
+ const numeric = toNumber(value);
22
+ if (numeric === null) return null;
23
+ return round(numeric / (10 ** USDC_DECIMALS), 6);
24
+ }
25
+
19
26
  function normalizeQuestion(question) {
20
27
  return String(question || '')
21
28
  .toLowerCase()
@@ -125,8 +132,8 @@ function derivePandoraYesPct(market) {
125
132
  return round(yesFromChance * 100, 6);
126
133
  }
127
134
 
128
- const reserveYes = toNumber(market && market.reserveYes);
129
- const reserveNo = toNumber(market && market.reserveNo);
135
+ const reserveYes = normalizeUsdcRawToUsd(market && market.reserveYes);
136
+ const reserveNo = normalizeUsdcRawToUsd(market && market.reserveNo);
130
137
  if (reserveYes === null || reserveNo === null) return null;
131
138
 
132
139
  const total = reserveYes + reserveNo;
@@ -251,8 +258,8 @@ async function fetchPandoraMarketContext(options = {}) {
251
258
  closeTimestamp: toNumber(market.marketCloseTimestamp) || toNumber(poll && poll.deadlineEpoch),
252
259
  yesPct,
253
260
  noPct,
254
- reserveYes: toNumber(market.reserveYes),
255
- reserveNo: toNumber(market.reserveNo),
261
+ reserveYes: normalizeUsdcRawToUsd(market.reserveYes),
262
+ reserveNo: normalizeUsdcRawToUsd(market.reserveNo),
256
263
  totalVolumeUsd: toNumber(market.totalVolume),
257
264
  tvlUsd: toNumber(market.currentTvl),
258
265
  diagnostics,
@@ -288,17 +295,20 @@ function buildGateChecks({
288
295
  const strictRuleCheckOk = bothRulesPresent && rulesEqual;
289
296
  checks.push({
290
297
  code: 'RULE_HASH_MATCH',
291
- ok: allowRuleMismatch ? true : strictRuleCheckOk,
292
- message: allowRuleMismatch
293
- ? 'Rule hash mismatch bypassed by --allow-rule-mismatch.'
294
- : bothRulesPresent
295
- ? 'Rule hashes must match.'
296
- : 'Rule text missing on one or both sides.',
298
+ ok: trustDeploy ? true : allowRuleMismatch ? true : strictRuleCheckOk,
299
+ message: trustDeploy
300
+ ? 'Rule hash mismatch bypassed by trusted deploy pairing.'
301
+ : allowRuleMismatch
302
+ ? 'Rule hash mismatch bypassed by --allow-rule-mismatch.'
303
+ : bothRulesPresent
304
+ ? 'Rule hashes must match.'
305
+ : 'Rule text missing on one or both sides.',
297
306
  meta: {
298
307
  left: ruleHashes.left,
299
308
  right: ruleHashes.right,
300
309
  bothRulesPresent,
301
310
  rulesEqual: bothRulesPresent ? rulesEqual : null,
311
+ trustDeploy: Boolean(trustDeploy),
302
312
  },
303
313
  });
304
314
 
@@ -751,7 +751,15 @@ function calculateExecutableDepthUsd(orderbook, side, slippageBps) {
751
751
 
752
752
  const limitPriceFactor = slippageBps / 10_000;
753
753
  const entries = side === 'buy' ? normalized.asks : normalized.bids;
754
- const priceLimit = side === 'buy' ? mid * (1 + limitPriceFactor) : mid * (1 - limitPriceFactor);
754
+ const bestBid = normalized.bids.length ? normalized.bids[0].price : null;
755
+ const bestAsk = normalized.asks.length ? normalized.asks[0].price : null;
756
+ const referencePrice =
757
+ side === 'buy'
758
+ ? (bestAsk !== null ? bestAsk : mid)
759
+ : (bestBid !== null ? bestBid : mid);
760
+ const priceLimit = side === 'buy'
761
+ ? referencePrice * (1 + limitPriceFactor)
762
+ : referencePrice * (1 - limitPriceFactor);
755
763
 
756
764
  let depthUsd = 0;
757
765
  let depthShares = 0;
@@ -770,6 +778,7 @@ function calculateExecutableDepthUsd(orderbook, side, slippageBps) {
770
778
  depthShares: round(depthShares, 6) || 0,
771
779
  worstPrice: worstPrice === null ? null : round(worstPrice, 8),
772
780
  midPrice: round(mid, 8),
781
+ referencePrice: round(referencePrice, 8),
773
782
  diagnostics: [],
774
783
  };
775
784
  }
@@ -850,7 +859,11 @@ async function fetchDepthForMarket(market, options = {}) {
850
859
  const noDepth = noBook ? calculateExecutableDepthUsd(noBook, 'buy', slippageBps) : null;
851
860
 
852
861
  const candidates = [yesDepth && yesDepth.depthUsd, noDepth && noDepth.depthUsd].filter((value) => Number.isFinite(value));
853
- const depthWithinSlippageUsd = candidates.length ? Math.min(...candidates) : 0;
862
+ const minDepthWithinSlippageUsd = candidates.length ? Math.min(...candidates) : 0;
863
+ const bestDepthWithinSlippageUsd = candidates.length ? Math.max(...candidates) : 0;
864
+ // Keep the legacy/conservative aggregate as min depth (used by sizing paths),
865
+ // while exposing best-depth separately for hedge-side diagnostics.
866
+ const depthWithinSlippageUsd = minDepthWithinSlippageUsd;
854
867
 
855
868
  if (!yesDepth) diagnostics.push('YES token orderbook unavailable.');
856
869
  if (!noDepth) diagnostics.push('NO token orderbook unavailable.');
@@ -884,6 +897,8 @@ async function fetchDepthForMarket(market, options = {}) {
884
897
  slippageBps,
885
898
  host: hostUsed || null,
886
899
  depthWithinSlippageUsd: round(depthWithinSlippageUsd, 6) || 0,
900
+ minDepthWithinSlippageUsd: round(minDepthWithinSlippageUsd, 6) || 0,
901
+ bestDepthWithinSlippageUsd: round(bestDepthWithinSlippageUsd, 6) || 0,
887
902
  yesDepth,
888
903
  noDepth,
889
904
  cacheFile,
package/cli/pandora.cjs CHANGED
@@ -21,7 +21,12 @@ const {
21
21
  daemonStatus: mirrorDaemonStatus,
22
22
  } = require('./lib/mirror_daemon_service.cjs');
23
23
  const { buildMirrorClosePlan } = require('./lib/mirror_close_service.cjs');
24
- const { fetchPolymarketPositionSummary } = require('./lib/polymarket_trade_adapter.cjs');
24
+ const {
25
+ fetchPolymarketPositionSummary,
26
+ resolvePolymarketMarket,
27
+ placeHedgeOrder,
28
+ readTradingCredsFromEnv,
29
+ } = require('./lib/polymarket_trade_adapter.cjs');
25
30
  const {
26
31
  runPolymarketCheck,
27
32
  runPolymarketApprove,
@@ -238,7 +243,7 @@ Usage:
238
243
  pandora [--output table|json] arbitrage [--chain-id <id>] [--venues pandora,polymarket] [--limit <n>] [--min-spread-pct <n>] [--min-liquidity-usdc <n>] [--max-close-diff-hours <n>] [--similarity-threshold <0-1>] [--cross-venue-only|--allow-same-venue] [--with-rules] [--include-similarity] [--question-contains <text>] [--polymarket-host <url>] [--polymarket-mock-url <url>]
239
244
  pandora [--output table|json] autopilot run|once --market-address <address> --side yes|no --amount-usdc <amount> [--trigger-yes-below <0-100>] [--trigger-yes-above <0-100>] [--paper|--execute-live] [--interval-ms <ms>] [--cooldown-ms <ms>] [--max-amount-usdc <amount>] [--max-open-exposure-usdc <amount>] [--max-trades-per-day <n>] [--state-file <path>] [--kill-switch-file <path>] [--webhook-url <url>] [--telegram-bot-token <token>] [--telegram-chat-id <id>] [--discord-webhook-url <url>]
240
245
  pandora [--output table|json] mirror browse|plan|deploy|verify|go|sync|status|close ...
241
- pandora [--output table|json] polymarket check|approve|preflight ...
246
+ pandora [--output table|json] polymarket check|approve|preflight|trade ...
242
247
  pandora [--output table|json] webhook test [--webhook-url <url>] [--webhook-template <json>] [--webhook-secret <secret>] [--telegram-bot-token <token>] [--telegram-chat-id <id>] [--discord-webhook-url <url>] [--webhook-timeout-ms <ms>] [--webhook-retries <n>]
243
248
  pandora [--output table|json] leaderboard [--metric profit|volume|win-rate] [--chain-id <id>] [--limit <n>] [--min-trades <n>]
244
249
  pandora [--output table|json] analyze --market-address <address> [--provider <name>] [--model <id>] [--max-cost-usd <n>] [--temperature <n>] [--timeout-ms <ms>]
@@ -269,10 +274,11 @@ Examples:
269
274
  pandora mirror browse --min-yes-pct 20 --max-yes-pct 80 --min-volume-24h 100000 --limit 10
270
275
  pandora mirror verify --pandora-market-address 0xabc... --polymarket-market-id 0xdef... --include-similarity
271
276
  pandora mirror go --polymarket-slug nba-mia-phi-2026-02-28 --liquidity-usdc 10 --paper
272
- pandora mirror sync once --pandora-market-address 0xabc... --polymarket-market-id 0xdef... --paper --hedge-ratio 1.0
277
+ pandora mirror sync once --pandora-market-address 0xabc... --polymarket-market-id 0xdef... --paper --hedge-ratio 1.0 --force-gate
273
278
  pandora polymarket check --rpc-url https://polygon-bor-rpc.publicnode.com --private-key 0x... --funder 0xproxy...
274
279
  pandora polymarket approve --dry-run --rpc-url https://polygon-bor-rpc.publicnode.com --private-key 0x... --funder 0xproxy...
275
280
  pandora polymarket preflight --rpc-url https://polygon-bor-rpc.publicnode.com --private-key 0x... --funder 0xproxy...
281
+ pandora polymarket trade --condition-id 0xabc... --token yes --amount-usdc 2 --dry-run
276
282
  pandora mirror close --pandora-market-address 0xabc... --polymarket-market-id 0xdef... --dry-run
277
283
  pandora webhook test --webhook-url https://example.com/hook --webhook-template '{\"text\":\"{{message}}\"}'
278
284
  pandora leaderboard --metric profit --limit 20
@@ -487,7 +493,7 @@ function helpJsonPayload() {
487
493
  'pandora [--output table|json] arbitrage ...',
488
494
  'pandora [--output table|json] autopilot run|once ...',
489
495
  'pandora [--output table|json] mirror plan|deploy|verify|sync|status ...',
490
- 'pandora [--output table|json] polymarket check|approve|preflight ...',
496
+ 'pandora [--output table|json] polymarket check|approve|preflight|trade ...',
491
497
  'pandora [--output table|json] webhook test ...',
492
498
  'pandora [--output table|json] leaderboard ...',
493
499
  'pandora [--output table|json] analyze ...',
@@ -3261,6 +3267,7 @@ function parseMirrorSyncFlags(args) {
3261
3267
  discordWebhookUrl: null,
3262
3268
  failOnWebhookError: false,
3263
3269
  daemon: false,
3270
+ forceGate: false,
3264
3271
  };
3265
3272
  let sawPaperModeFlag = false;
3266
3273
  let sawExecuteLiveModeFlag = false;
@@ -3451,6 +3458,10 @@ function parseMirrorSyncFlags(args) {
3451
3458
  options.trustDeploy = true;
3452
3459
  continue;
3453
3460
  }
3461
+ if (token === '--force-gate' || token === '--skip-gate') {
3462
+ options.forceGate = true;
3463
+ continue;
3464
+ }
3454
3465
  if (token === '--manifest-file') {
3455
3466
  options.manifestFile = requireFlagValue(rest, i, '--manifest-file');
3456
3467
  i += 1;
@@ -3508,6 +3519,7 @@ function parseMirrorSyncFlags(args) {
3508
3519
  hedgeEnabled: options.hedgeEnabled,
3509
3520
  hedgeRatio: options.hedgeRatio,
3510
3521
  hedgeTriggerUsdc: options.hedgeTriggerUsdc,
3522
+ forceGate: options.forceGate,
3511
3523
  });
3512
3524
  }
3513
3525
  if (options.killSwitchFile === null) {
@@ -3563,6 +3575,7 @@ function buildMirrorSyncStrategy(options) {
3563
3575
  hedgeEnabled: options.hedgeEnabled,
3564
3576
  hedgeRatio: options.hedgeRatio,
3565
3577
  hedgeTriggerUsdc: options.hedgeTriggerUsdc,
3578
+ forceGate: options.forceGate,
3566
3579
  };
3567
3580
  }
3568
3581
 
@@ -3628,6 +3641,7 @@ function buildMirrorSyncDaemonCliArgs(options, shared) {
3628
3641
  if (options.polymarketGammaMockUrl) args.push('--polymarket-gamma-mock-url', options.polymarketGammaMockUrl);
3629
3642
  if (options.polymarketMockUrl) args.push('--polymarket-mock-url', options.polymarketMockUrl);
3630
3643
  if (options.trustDeploy) args.push('--trust-deploy');
3644
+ if (options.forceGate) args.push('--force-gate');
3631
3645
  if (options.manifestFile) args.push('--manifest-file', options.manifestFile);
3632
3646
 
3633
3647
  if (options.webhookUrl) args.push('--webhook-url', options.webhookUrl);
@@ -3676,6 +3690,7 @@ function parseMirrorGoFlags(args) {
3676
3690
  sources: [],
3677
3691
  manifestFile: null,
3678
3692
  trustDeploy: false,
3693
+ forceGate: false,
3679
3694
  polymarketHost: null,
3680
3695
  polymarketGammaUrl: null,
3681
3696
  polymarketGammaMockUrl: null,
@@ -3857,6 +3872,10 @@ function parseMirrorGoFlags(args) {
3857
3872
  options.trustDeploy = true;
3858
3873
  continue;
3859
3874
  }
3875
+ if (token === '--force-gate' || token === '--skip-gate') {
3876
+ options.forceGate = true;
3877
+ continue;
3878
+ }
3860
3879
  if (token === '--polymarket-host') {
3861
3880
  options.polymarketHost = requireFlagValue(args, i, '--polymarket-host');
3862
3881
  i += 1;
@@ -4041,6 +4060,102 @@ function parsePolymarketApproveFlags(args) {
4041
4060
  return options;
4042
4061
  }
4043
4062
 
4063
+ function parsePolymarketTradeFlags(args) {
4064
+ const options = {
4065
+ conditionId: null,
4066
+ slug: null,
4067
+ token: null,
4068
+ tokenId: null,
4069
+ side: 'buy',
4070
+ amountUsdc: null,
4071
+ dryRun: false,
4072
+ execute: false,
4073
+ host: null,
4074
+ rpcUrl: null,
4075
+ privateKey: null,
4076
+ funder: null,
4077
+ };
4078
+
4079
+ const sharedArgs = [];
4080
+ for (let i = 0; i < args.length; i += 1) {
4081
+ const token = args[i];
4082
+ if (token === '--condition-id' || token === '--market-id') {
4083
+ options.conditionId = requireFlagValue(args, i, token);
4084
+ i += 1;
4085
+ continue;
4086
+ }
4087
+ if (token === '--slug') {
4088
+ options.slug = requireFlagValue(args, i, '--slug');
4089
+ i += 1;
4090
+ continue;
4091
+ }
4092
+ if (token === '--token') {
4093
+ const value = String(requireFlagValue(args, i, '--token')).trim().toLowerCase();
4094
+ if (!['yes', 'no'].includes(value)) {
4095
+ throw new CliError('INVALID_FLAG_VALUE', '--token must be yes|no.');
4096
+ }
4097
+ options.token = value;
4098
+ i += 1;
4099
+ continue;
4100
+ }
4101
+ if (token === '--token-id') {
4102
+ options.tokenId = requireFlagValue(args, i, '--token-id');
4103
+ i += 1;
4104
+ continue;
4105
+ }
4106
+ if (token === '--side') {
4107
+ const value = String(requireFlagValue(args, i, '--side')).trim().toLowerCase();
4108
+ if (!['buy', 'sell'].includes(value)) {
4109
+ throw new CliError('INVALID_FLAG_VALUE', '--side must be buy|sell.');
4110
+ }
4111
+ options.side = value;
4112
+ i += 1;
4113
+ continue;
4114
+ }
4115
+ if (token === '--amount-usdc') {
4116
+ options.amountUsdc = parsePositiveNumber(requireFlagValue(args, i, '--amount-usdc'), '--amount-usdc');
4117
+ i += 1;
4118
+ continue;
4119
+ }
4120
+ if (token === '--polymarket-host') {
4121
+ options.host = requireFlagValue(args, i, '--polymarket-host');
4122
+ i += 1;
4123
+ continue;
4124
+ }
4125
+ if (token === '--dry-run') {
4126
+ options.dryRun = true;
4127
+ continue;
4128
+ }
4129
+ if (token === '--execute') {
4130
+ options.execute = true;
4131
+ continue;
4132
+ }
4133
+ sharedArgs.push(token);
4134
+ }
4135
+
4136
+ if (options.dryRun === options.execute) {
4137
+ throw new CliError('INVALID_ARGS', 'polymarket trade requires exactly one mode: --dry-run or --execute.');
4138
+ }
4139
+ if (options.amountUsdc === null) {
4140
+ throw new CliError('MISSING_REQUIRED_FLAG', 'Missing --amount-usdc <amount>.');
4141
+ }
4142
+ if (!options.tokenId && !options.token) {
4143
+ throw new CliError('MISSING_REQUIRED_FLAG', 'Provide --token yes|no (or --token-id <id>).');
4144
+ }
4145
+ if (!options.tokenId && !options.conditionId && !options.slug) {
4146
+ throw new CliError(
4147
+ 'MISSING_REQUIRED_FLAG',
4148
+ 'Provide --condition-id <id> or --slug <slug> when --token-id is not set.',
4149
+ );
4150
+ }
4151
+
4152
+ const shared = parsePolymarketSharedFlags(sharedArgs, 'trade');
4153
+ options.rpcUrl = shared.rpcUrl;
4154
+ options.privateKey = shared.privateKey;
4155
+ options.funder = shared.funder;
4156
+ return options;
4157
+ }
4158
+
4044
4159
  function parseWebhookTestFlags(args) {
4045
4160
  const options = {
4046
4161
  webhookUrl: null,
@@ -4486,7 +4601,8 @@ async function rpcRequest(rpcUrl, method, params, timeoutMs) {
4486
4601
  return payload.result;
4487
4602
  }
4488
4603
 
4489
- async function probeHttpEndpoint(url, timeoutMs, method = 'HEAD') {
4604
+ async function probeHttpEndpoint(url, timeoutMs, method = 'HEAD', options = {}) {
4605
+ const acceptAnyHttpStatus = Boolean(options.acceptAnyHttpStatus);
4490
4606
  const controller = new AbortController();
4491
4607
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
4492
4608
  try {
@@ -4494,10 +4610,13 @@ async function probeHttpEndpoint(url, timeoutMs, method = 'HEAD') {
4494
4610
  method,
4495
4611
  signal: controller.signal,
4496
4612
  });
4613
+ const reachable = acceptAnyHttpStatus
4614
+ ? response.status >= 100 && response.status < 500
4615
+ : response.ok;
4497
4616
  return {
4498
- ok: response.ok,
4617
+ ok: reachable,
4499
4618
  status: response.status,
4500
- error: response.ok ? null : `HTTP ${response.status}`,
4619
+ error: reachable ? null : `HTTP ${response.status}`,
4501
4620
  };
4502
4621
  } catch (err) {
4503
4622
  if (err && err.name === 'AbortError') {
@@ -4707,10 +4826,15 @@ async function buildDoctorReport(options) {
4707
4826
  }
4708
4827
 
4709
4828
  if (shouldCheckPolymarket) {
4710
- const hostProbe = await probeHttpEndpoint(report.polymarket.host, options.rpcTimeoutMs, 'HEAD');
4829
+ const polymarketProbeTarget = `${String(report.polymarket.host).replace(/\/+$/, '')}/time`;
4830
+ const hostProbe = await probeHttpEndpoint(polymarketProbeTarget, options.rpcTimeoutMs, 'GET', {
4831
+ acceptAnyHttpStatus: true,
4832
+ });
4711
4833
  report.polymarket.hostReachability = hostProbe;
4712
4834
  if (!hostProbe.ok) {
4713
- report.polymarket.failures.push(`Polymarket host reachability failed: ${hostProbe.error || 'unknown error'}`);
4835
+ report.polymarket.failures.push(
4836
+ `Polymarket host reachability failed (${polymarketProbeTarget}): ${hostProbe.error || 'unknown error'}`,
4837
+ );
4714
4838
  }
4715
4839
 
4716
4840
  let polyCheck = null;
@@ -5735,7 +5859,26 @@ function renderSuggestTable(data) {
5735
5859
  }
5736
5860
 
5737
5861
  function renderSingleEntityTable(data) {
5738
- printRecord(data.item);
5862
+ if (data && typeof data.item === 'object' && data.item !== null) {
5863
+ printRecord(data.item);
5864
+ return;
5865
+ }
5866
+ if (Array.isArray(data && data.items)) {
5867
+ if (!data.items.length) {
5868
+ console.log('No items found.');
5869
+ return;
5870
+ }
5871
+ for (const item of data.items) {
5872
+ printRecord(item);
5873
+ console.log('');
5874
+ }
5875
+ return;
5876
+ }
5877
+ if (data && typeof data === 'object') {
5878
+ printRecord(data);
5879
+ return;
5880
+ }
5881
+ console.log(String(data));
5739
5882
  }
5740
5883
 
5741
5884
  function renderMarketsGetTable(data) {
@@ -8086,7 +8229,11 @@ function renderMirrorSyncTickLine(tickContext, outputMode) {
8086
8229
  const action = snapshot.action || null;
8087
8230
  const actionStatus = action && action.status ? action.status : 'idle';
8088
8231
  const gateCode =
8089
- action && Array.isArray(action.failedChecks) && action.failedChecks.length ? action.failedChecks[0] : '';
8232
+ action && Array.isArray(action.failedChecks) && action.failedChecks.length
8233
+ ? action.forcedGateBypass
8234
+ ? `forced:${action.failedChecks[0]}`
8235
+ : action.failedChecks[0]
8236
+ : '';
8090
8237
 
8091
8238
  if (outputMode === 'json') {
8092
8239
  console.log(
@@ -8166,10 +8313,10 @@ async function runMirrorCommand(args, context) {
8166
8313
  ' verify --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--trust-deploy] [--manifest-file <path>] [--include-similarity] [--with-rules] [--allow-rule-mismatch]',
8167
8314
  );
8168
8315
  console.log(
8169
- ' go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once]',
8316
+ ' go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once] [--force-gate]',
8170
8317
  );
8171
8318
  console.log(
8172
- ' sync run|once|start|stop|status --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--state-file <path>] [--kill-switch-file <path>]',
8319
+ ' sync run|once|start|stop|status --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--force-gate] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--state-file <path>] [--kill-switch-file <path>]',
8173
8320
  );
8174
8321
  console.log(' status --state-file <path>|--strategy-hash <hash> [--with-live] [--trust-deploy]');
8175
8322
  console.log(' close --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute');
@@ -8470,7 +8617,7 @@ async function runMirrorCommand(args, context) {
8470
8617
  if (action === 'go') {
8471
8618
  if (includesHelpFlag(shared.rest)) {
8472
8619
  const usage =
8473
- 'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once]';
8620
+ 'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once] [--force-gate]';
8474
8621
  if (context.outputMode === 'json') {
8475
8622
  emitSuccess(context.outputMode, 'mirror.go.help', commandHelpPayload(usage));
8476
8623
  } else {
@@ -8611,6 +8758,7 @@ async function runMirrorCommand(args, context) {
8611
8758
  polymarketGammaMockUrl: options.polymarketGammaMockUrl,
8612
8759
  polymarketMockUrl: options.polymarketMockUrl,
8613
8760
  trustDeploy,
8761
+ forceGate: options.forceGate,
8614
8762
  webhookUrl: null,
8615
8763
  webhookTemplate: null,
8616
8764
  webhookSecret: null,
@@ -8672,6 +8820,7 @@ async function runMirrorCommand(args, context) {
8672
8820
  '--paper',
8673
8821
  `--drift-trigger-bps ${options.driftTriggerBps}`,
8674
8822
  `--hedge-trigger-usdc ${options.hedgeTriggerUsdc}`,
8823
+ options.forceGate ? '--force-gate' : null,
8675
8824
  ]
8676
8825
  .filter(Boolean)
8677
8826
  .join(' ');
@@ -8714,7 +8863,7 @@ async function runMirrorCommand(args, context) {
8714
8863
  'mirror.sync.help',
8715
8864
  {
8716
8865
  usage:
8717
- 'pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--min-time-to-close-sec <n>] [--state-file <path>] [--kill-switch-file <path>]',
8866
+ 'pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--force-gate] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--min-time-to-close-sec <n>] [--state-file <path>] [--kill-switch-file <path>]',
8718
8867
  daemonLifecycle: {
8719
8868
  start:
8720
8869
  'pandora [--output table|json] mirror sync start --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [run flags]',
@@ -8743,7 +8892,7 @@ async function runMirrorCommand(args, context) {
8743
8892
  );
8744
8893
  } else {
8745
8894
  console.log(
8746
- 'Usage: pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--min-time-to-close-sec <n>] [--state-file <path>] [--kill-switch-file <path>]',
8895
+ 'Usage: pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--execute-live] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--force-gate] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--min-time-to-close-sec <n>] [--state-file <path>] [--kill-switch-file <path>]',
8747
8896
  );
8748
8897
  console.log('Daemon stop: pandora mirror sync stop --pid-file <path>|--strategy-hash <hash>');
8749
8898
  console.log('Daemon status: pandora mirror sync status --pid-file <path>|--strategy-hash <hash>');
@@ -8980,7 +9129,7 @@ async function runPolymarketCommand(args, context) {
8980
9129
  const actionArgs = args.slice(1);
8981
9130
 
8982
9131
  if (!action || action === '--help' || action === '-h') {
8983
- const usage = 'pandora [--output table|json] polymarket check|approve|preflight ...';
9132
+ const usage = 'pandora [--output table|json] polymarket check|approve|preflight|trade ...';
8984
9133
  if (context.outputMode === 'json') {
8985
9134
  emitSuccess(context.outputMode, 'polymarket.help', commandHelpPayload(usage));
8986
9135
  } else {
@@ -8990,6 +9139,9 @@ async function runPolymarketCommand(args, context) {
8990
9139
  console.log(' check [--rpc-url <url>] [--private-key <hex>] [--funder <address>]');
8991
9140
  console.log(' approve --dry-run|--execute [--rpc-url <url>] [--private-key <hex>] [--funder <address>]');
8992
9141
  console.log(' preflight [--rpc-url <url>] [--private-key <hex>] [--funder <address>]');
9142
+ console.log(
9143
+ ' trade --condition-id <id>|--slug <slug>|--token-id <id> --token yes|no --amount-usdc <n> --dry-run|--execute [--side buy|sell] [--polymarket-host <url>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>]',
9144
+ );
8993
9145
  }
8994
9146
  return;
8995
9147
  }
@@ -9064,7 +9216,102 @@ async function runPolymarketCommand(args, context) {
9064
9216
  return;
9065
9217
  }
9066
9218
 
9067
- throw new CliError('INVALID_ARGS', 'polymarket requires subcommand: check|approve|preflight');
9219
+ if (action === 'trade') {
9220
+ if (includesHelpFlag(actionArgs)) {
9221
+ const usage =
9222
+ 'pandora [--output table|json] polymarket trade --condition-id <id>|--slug <slug>|--token-id <id> --token yes|no --amount-usdc <n> --dry-run|--execute [--side buy|sell] [--polymarket-host <url>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>]';
9223
+ if (context.outputMode === 'json') {
9224
+ emitSuccess(context.outputMode, 'polymarket.trade.help', commandHelpPayload(usage));
9225
+ } else {
9226
+ console.log(`Usage: ${usage}`);
9227
+ }
9228
+ return;
9229
+ }
9230
+
9231
+ const options = parsePolymarketTradeFlags(actionArgs);
9232
+ let market = null;
9233
+ let tokenId = options.tokenId;
9234
+ if (!tokenId) {
9235
+ market = await resolvePolymarketMarket({
9236
+ host: options.host || process.env.POLYMARKET_HOST || null,
9237
+ timeoutMs: DEFAULT_INDEXER_TIMEOUT_MS,
9238
+ marketId: options.conditionId,
9239
+ slug: options.slug,
9240
+ });
9241
+ tokenId = options.token === 'yes' ? market.yesTokenId : market.noTokenId;
9242
+ if (!tokenId) {
9243
+ throw new CliError(
9244
+ 'POLYMARKET_TOKEN_MAPPING_FAILED',
9245
+ `Unable to resolve ${String(options.token || '').toUpperCase()} token id for target market.`,
9246
+ {
9247
+ conditionId: options.conditionId,
9248
+ slug: options.slug,
9249
+ market,
9250
+ },
9251
+ );
9252
+ }
9253
+ }
9254
+
9255
+ if (options.dryRun) {
9256
+ emitSuccess(context.outputMode, 'polymarket.trade', {
9257
+ mode: 'dry-run',
9258
+ status: 'planned',
9259
+ conditionId: options.conditionId || (market && market.marketId) || null,
9260
+ slug: options.slug || (market && market.slug) || null,
9261
+ token: options.token || null,
9262
+ tokenId,
9263
+ side: options.side,
9264
+ amountUsdc: options.amountUsdc,
9265
+ host: options.host || process.env.POLYMARKET_HOST || null,
9266
+ }, renderSingleEntityTable);
9267
+ return;
9268
+ }
9269
+
9270
+ const envCreds = readTradingCredsFromEnv();
9271
+ let result;
9272
+ try {
9273
+ result = await placeHedgeOrder({
9274
+ host: options.host || envCreds.host || null,
9275
+ tokenId,
9276
+ side: options.side,
9277
+ amountUsd: options.amountUsdc,
9278
+ privateKey: options.privateKey || envCreds.privateKey,
9279
+ funder: options.funder || envCreds.funder,
9280
+ apiKey: envCreds.apiKey,
9281
+ apiSecret: envCreds.apiSecret,
9282
+ apiPassphrase: envCreds.apiPassphrase,
9283
+ });
9284
+ } catch (err) {
9285
+ throw new CliError(
9286
+ 'POLYMARKET_TRADE_FAILED',
9287
+ err && err.message ? err.message : 'Polymarket trade execution failed.',
9288
+ { cause: err && err.message ? err.message : String(err) },
9289
+ );
9290
+ }
9291
+
9292
+ if (!result || result.ok === false) {
9293
+ throw new CliError(
9294
+ 'POLYMARKET_TRADE_FAILED',
9295
+ result && result.error && result.error.message ? result.error.message : 'Polymarket order was rejected.',
9296
+ { result },
9297
+ );
9298
+ }
9299
+
9300
+ emitSuccess(context.outputMode, 'polymarket.trade', {
9301
+ mode: 'execute',
9302
+ status: 'submitted',
9303
+ conditionId: options.conditionId || (market && market.marketId) || null,
9304
+ slug: options.slug || (market && market.slug) || null,
9305
+ token: options.token || null,
9306
+ tokenId,
9307
+ side: options.side,
9308
+ amountUsdc: options.amountUsdc,
9309
+ result,
9310
+ }, renderSingleEntityTable);
9311
+ return;
9312
+ }
9313
+
9314
+ throw new CliError('INVALID_ARGS', 'polymarket requires subcommand: check|approve|preflight|trade');
9068
9315
  }
9069
9316
 
9070
9317
  async function runWebhookCommand(args, context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pandora-cli-skills",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "Pandora CLI & Skills",
5
5
  "main": "cli/pandora.cjs",
6
6
  "bin": {