pandora-cli-skills 1.1.48 → 1.1.49

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.48
4
+ version: 1.1.49
5
5
  ---
6
6
 
7
7
  # Pandora CLI & Skills
@@ -43,6 +43,24 @@ function buildMirrorDeployRetryCommand(cliName) {
43
43
  return `${cliName} mirror deploy --dry-run --plan-file <plan-file>`;
44
44
  }
45
45
 
46
+ function buildMirrorVerifyRetryCommand(cliName, details) {
47
+ const pandoraMarketAddress = toAddressOrPlaceholder(details && details.pandoraMarketAddress);
48
+ const polymarketMarketId = cleanToken(details && details.polymarketMarketId, '');
49
+ const polymarketSlug = cleanToken(details && details.polymarketSlug, '');
50
+ const manifestFile = cleanToken(details && details.manifestFile, '');
51
+ const command = [
52
+ `${cliName} mirror verify`,
53
+ `--market-address ${pandoraMarketAddress}`,
54
+ polymarketMarketId ? `--polymarket-market-id ${polymarketMarketId}` : null,
55
+ !polymarketMarketId && polymarketSlug ? `--polymarket-slug ${polymarketSlug}` : null,
56
+ '--trust-deploy',
57
+ manifestFile ? `--manifest-file ${manifestFile}` : null,
58
+ ]
59
+ .filter(Boolean)
60
+ .join(' ');
61
+ return command;
62
+ }
63
+
46
64
  function buildMirrorSyncRetryCommand(cliName) {
47
65
  return `${cliName} mirror sync once --paper --pandora-market-address <address> --polymarket-market-id <id>`;
48
66
  }
@@ -197,13 +215,19 @@ function createErrorRecoveryService(options = {}) {
197
215
  };
198
216
  case 'MIRROR_DEPLOY_FAILED':
199
217
  case 'MIRROR_GO_FAILED':
200
- case 'MIRROR_GO_VERIFY_FAILED':
201
218
  case 'MIRROR_GO_PREFLIGHT_FAILED':
202
219
  return {
203
220
  action: 'Re-run mirror deploy/verify in dry-run mode',
204
221
  command: buildMirrorDeployRetryCommand(cliName),
205
222
  retryable: true,
206
223
  };
224
+ case 'MIRROR_GO_VERIFY_FAILED':
225
+ case 'MIRROR_GO_VERIFY_PENDING':
226
+ return {
227
+ action: 'Retry verification against the existing deployed market (do not redeploy)',
228
+ command: buildMirrorVerifyRetryCommand(cliName, details),
229
+ retryable: true,
230
+ };
207
231
  case 'MIRROR_SYNC_FAILED':
208
232
  case 'MIRROR_GO_SYNC_FAILED':
209
233
  case 'MIRROR_SYNC_PREFLIGHT_FAILED':
@@ -14,6 +14,8 @@ function createRunLpCommand(deps) {
14
14
  const includesHelpFlag = requireDep(deps, 'includesHelpFlag');
15
15
  const emitSuccess = requireDep(deps, 'emitSuccess');
16
16
  const commandHelpPayload = requireDep(deps, 'commandHelpPayload');
17
+ const parseIndexerSharedFlags = requireDep(deps, 'parseIndexerSharedFlags');
18
+ const maybeLoadTradeEnv = requireDep(deps, 'maybeLoadTradeEnv');
17
19
  const parseLpFlags = requireDep(deps, 'parseLpFlags');
18
20
  const runLp = requireDep(deps, 'runLp');
19
21
  const renderSingleEntityTable = requireDep(deps, 'renderSingleEntityTable');
@@ -27,18 +29,26 @@ function createRunLpCommand(deps) {
27
29
  context.outputMode,
28
30
  'lp.help',
29
31
  commandHelpPayload(
30
- 'pandora [--output table|json] lp add|remove|positions [--market-address <address>] [--wallet <address>] [--amount-usdc <n>] [--lp-tokens <n>] [--dry-run|--execute] [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>] [--deadline-seconds <n>] [--indexer-url <url>] [--timeout-ms <ms>]',
32
+ 'pandora [--output table|json] lp add|remove|positions [--market-address <address>] [--wallet <address>] [--amount-usdc <n>] [--lp-tokens <n>|--all] [--dry-run|--execute] [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>] [--deadline-seconds <n>] [--indexer-url <url>] [--timeout-ms <ms>]',
31
33
  ),
32
34
  );
33
35
  } else {
34
36
  // eslint-disable-next-line no-console
35
37
  console.log(
36
- 'Usage: pandora [--output table|json] lp add|remove|positions [--market-address <address>] [--wallet <address>] [--amount-usdc <n>] [--lp-tokens <n>] [--dry-run|--execute] [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>] [--deadline-seconds <n>] [--indexer-url <url>] [--timeout-ms <ms>]',
38
+ 'Usage: pandora [--output table|json] lp add|remove|positions [--market-address <address>] [--wallet <address>] [--amount-usdc <n>] [--lp-tokens <n>|--all] [--dry-run|--execute] [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>] [--deadline-seconds <n>] [--indexer-url <url>] [--timeout-ms <ms>]',
37
39
  );
38
40
  }
39
41
  return;
40
42
  }
41
- const options = parseLpFlags(args);
43
+ const shared = parseIndexerSharedFlags(args);
44
+ maybeLoadTradeEnv(shared);
45
+ const options = parseLpFlags(shared.rest);
46
+ if (shared.indexerUrl) {
47
+ options.indexerUrl = shared.indexerUrl;
48
+ }
49
+ if (Number.isFinite(shared.timeoutMs)) {
50
+ options.timeoutMs = shared.timeoutMs;
51
+ }
42
52
  if (options.execute && options.action !== 'positions' && assertLiveWriteAllowed) {
43
53
  await assertLiveWriteAllowed(`lp.${options.action}.execute`, {
44
54
  notionalUsdc: options.action === 'add' ? options.amountUsdc : null,
@@ -813,7 +813,7 @@ async function runLpRemove(options = {}) {
813
813
  status: options.execute ? 'submitted' : 'planned',
814
814
  action: 'remove',
815
815
  marketAddress: options.marketAddress,
816
- lpTokens: options.lpTokens,
816
+ lpTokens: options.lpAll ? 'all' : options.lpTokens,
817
817
  deadlineSeconds,
818
818
  txPlan: null,
819
819
  preflight: null,
@@ -835,6 +835,20 @@ async function runLpRemove(options = {}) {
835
835
 
836
836
  if (!options.execute) {
837
837
  const deadline = BigInt(Math.floor(Date.now() / 1000) + deadlineSeconds);
838
+ if (options.lpAll) {
839
+ payload.txPlan = {
840
+ lpTokenDecimalsAssumed: 18,
841
+ sharesToBurnRaw: null,
842
+ sharesToBurnMode: 'all-onchain-balance-at-execution',
843
+ minCollateralOutRaw: '0',
844
+ deadline: deadline.toString(),
845
+ removeLiquidityArgOrder: 'sharesToBurn, minCollateralOut, deadline',
846
+ };
847
+ payload.diagnostics.push(
848
+ 'Dry-run with --all defers LP token amount resolution to execution-time on-chain balance.',
849
+ );
850
+ return payload;
851
+ }
838
852
  payload.txPlan = {
839
853
  lpTokenDecimalsAssumed: 18,
840
854
  sharesToBurnRaw: parseUnits(String(options.lpTokens), 18).toString(),
@@ -849,7 +863,26 @@ async function runLpRemove(options = {}) {
849
863
  await ensureContractCode(publicClient, marketAddress, 'Market');
850
864
 
851
865
  const lpTokenDecimals = await readDecimals(publicClient, marketAddress, 18);
852
- const sharesToBurnRaw = parseUnits(String(options.lpTokens), lpTokenDecimals);
866
+ let sharesToBurnRaw;
867
+ if (options.lpAll) {
868
+ const balanceRaw = await publicClient.readContract({
869
+ address: marketAddress,
870
+ abi: LP_TOKEN_ABI,
871
+ functionName: 'balanceOf',
872
+ args: [account.address],
873
+ });
874
+ if (typeof balanceRaw !== 'bigint' || balanceRaw <= 0n) {
875
+ throw createServiceError('LP_REMOVE_ZERO_BALANCE', 'No LP token balance available to remove with --all.', {
876
+ marketAddress,
877
+ account: account.address,
878
+ });
879
+ }
880
+ sharesToBurnRaw = balanceRaw;
881
+ payload.lpTokens = formatUnits(balanceRaw, lpTokenDecimals);
882
+ payload.diagnostics.push('Using full LP token balance (--all) from on-chain balanceOf(account).');
883
+ } else {
884
+ sharesToBurnRaw = parseUnits(String(options.lpTokens), lpTokenDecimals);
885
+ }
853
886
  const deadline = BigInt(Math.floor(Date.now() / 1000) + deadlineSeconds);
854
887
  const minCollateralOutRaw = 0n;
855
888
  const preview = await readCalcRemoveLiquidity(publicClient, marketAddress, sharesToBurnRaw);
@@ -4,7 +4,7 @@
4
4
  * @type {string}
5
5
  */
6
6
  const MIRROR_GO_USAGE =
7
- 'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--paper|--dry-run|--execute-live|--execute] [--auto-sync] [--sync-once] [--sync-interval-ms <ms>] [--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>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>] [--usdc <address>] [--oracle <address>] [--factory <address>] [--sources <url...>] [--manifest-file <path>] [--trust-deploy] [--skip-gate] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--with-rules] [--include-similarity] [--min-close-lead-seconds <n>]';
7
+ 'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--paper|--dry-run|--execute-live|--execute] [--auto-sync] [--sync-once] [--sync-interval-ms <ms>] [--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>] [--chain-id <id>] [--rpc-url <url>] [--polymarket-rpc-url <url>] [--private-key <hex>] [--funder <address>] [--usdc <address>] [--oracle <address>] [--factory <address>] [--sources <url...>] [--manifest-file <path>] [--trust-deploy] [--skip-gate] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--with-rules] [--include-similarity] [--min-close-lead-seconds <n>]';
8
8
 
9
9
  /**
10
10
  * Canonical usage string for `mirror sync`.
@@ -12,7 +12,7 @@ const MIRROR_GO_USAGE =
12
12
  * @type {string}
13
13
  */
14
14
  const MIRROR_SYNC_USAGE =
15
- 'pandora [--output table|json] mirror sync run|once|start --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--usdc <address>] [--trust-deploy] [--manifest-file <path>] [--skip-gate] [--daemon] [--stream|--no-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>] [--depth-slippage-bps <n>] [--min-time-to-close-sec <n>] [--iterations <n>] [--state-file <path>] [--kill-switch-file <path>] [--chain-id <id>] [--rpc-url <url>] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--webhook-url <url>] [--telegram-bot-token <token>] [--telegram-chat-id <id>] [--discord-webhook-url <url>]';
15
+ 'pandora [--output table|json] mirror sync run|once|start --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--usdc <address>] [--trust-deploy] [--manifest-file <path>] [--skip-gate] [--daemon] [--stream|--no-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>] [--depth-slippage-bps <n>] [--min-time-to-close-sec <n>] [--iterations <n>] [--state-file <path>] [--kill-switch-file <path>] [--chain-id <id>] [--rpc-url <url>] [--polymarket-rpc-url <url>] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--webhook-url <url>] [--telegram-bot-token <token>] [--telegram-chat-id <id>] [--discord-webhook-url <url>]';
16
16
 
17
17
  const INVALID_SUBCOMMAND_MESSAGE =
18
18
  'mirror requires subcommand: browse|plan|deploy|verify|lp-explain|hedge-calc|simulate|go|sync|status|close';
@@ -16,6 +16,9 @@ module.exports = async function handleMirrorGo({ shared, context, deps, mirrorGo
16
16
  buildMirrorPlan,
17
17
  deployMirror,
18
18
  resolveTrustedDeployPair,
19
+ findMirrorPair,
20
+ defaultMirrorManifestFile,
21
+ hasContractCodeAtAddress,
19
22
  verifyMirror,
20
23
  runLivePolymarketPreflightForMirror,
21
24
  runMirrorSync,
@@ -46,32 +49,19 @@ module.exports = async function handleMirrorGo({ shared, context, deps, mirrorGo
46
49
  console.error(`Warning: ${deprecatedForceGateWarning}`);
47
50
  }
48
51
 
52
+ const diagnostics = [];
53
+ if (deprecatedForceGateWarning) diagnostics.push(deprecatedForceGateWarning);
49
54
  let planPayload;
50
- let deployPayload;
51
55
  try {
52
56
  planPayload = await buildMirrorPlan({
53
57
  ...options,
54
58
  indexerUrl,
55
59
  timeoutMs: shared.timeoutMs,
56
60
  });
57
- if (options.executeLive && typeof assertLiveWriteAllowed === 'function') {
58
- await assertLiveWriteAllowed('mirror.go.deploy.execute', {
59
- notionalUsdc: options.liquidityUsdc,
60
- runtimeMode: 'live',
61
- });
62
- }
63
- deployPayload = await deployMirror({
64
- ...options,
65
- planData: planPayload,
66
- execute: options.executeLive,
67
- indexerUrl,
68
- timeoutMs: shared.timeoutMs,
69
- });
70
61
  } catch (err) {
71
62
  throw coerceMirrorServiceError(err, 'MIRROR_GO_FAILED');
72
63
  }
73
64
 
74
- const pandoraMarketAddress = deployPayload && deployPayload.pandora ? deployPayload.pandora.marketAddress : null;
75
65
  const sourceSelector = {
76
66
  polymarketMarketId:
77
67
  (planPayload && planPayload.sourceMarket && planPayload.sourceMarket.marketId) || options.polymarketMarketId || null,
@@ -79,17 +69,115 @@ module.exports = async function handleMirrorGo({ shared, context, deps, mirrorGo
79
69
  (planPayload && planPayload.sourceMarket && planPayload.sourceMarket.slug) || options.polymarketSlug || null,
80
70
  };
81
71
 
72
+ let reusedManifestLookup = null;
73
+ if (
74
+ options.executeLive &&
75
+ typeof findMirrorPair === 'function' &&
76
+ (sourceSelector.polymarketMarketId || sourceSelector.polymarketSlug)
77
+ ) {
78
+ const manifestFilePath =
79
+ options.manifestFile || (typeof defaultMirrorManifestFile === 'function' ? defaultMirrorManifestFile() : null);
80
+ if (manifestFilePath) {
81
+ try {
82
+ const lookup = findMirrorPair(manifestFilePath, sourceSelector);
83
+ if (
84
+ lookup &&
85
+ lookup.pair &&
86
+ lookup.pair.trusted !== false &&
87
+ lookup.pair.pandoraMarketAddress
88
+ ) {
89
+ let allowReuse = true;
90
+ if (typeof hasContractCodeAtAddress === 'function') {
91
+ try {
92
+ const hasCode = await hasContractCodeAtAddress({
93
+ marketAddress: lookup.pair.pandoraMarketAddress,
94
+ chainId: options.chainId,
95
+ rpcUrl: options.rpcUrl,
96
+ });
97
+ if (!hasCode) {
98
+ allowReuse = false;
99
+ diagnostics.push(
100
+ `Trusted mirror pair exists but has no bytecode at ${lookup.pair.pandoraMarketAddress}; continuing with fresh deploy.`,
101
+ );
102
+ }
103
+ } catch (err) {
104
+ diagnostics.push(
105
+ `On-chain deploy dedupe probe failed (${lookup.pair.pandoraMarketAddress}): ${err && err.message ? err.message : String(err)}. Conservatively reusing trusted pair to avoid duplicate spend.`,
106
+ );
107
+ }
108
+ }
109
+ if (allowReuse) {
110
+ reusedManifestLookup = lookup;
111
+ diagnostics.push(
112
+ `Trusted mirror pair already exists (${lookup.pair.pandoraMarketAddress}); deploy step skipped to prevent duplicate market creation.`,
113
+ );
114
+ }
115
+ }
116
+ } catch (err) {
117
+ diagnostics.push(`Mirror manifest lookup failed: ${err && err.message ? err.message : String(err)}`);
118
+ }
119
+ }
120
+ }
121
+
122
+ let deployPayload;
123
+ try {
124
+ if (reusedManifestLookup) {
125
+ deployPayload = {
126
+ schemaVersion: '1.0.0',
127
+ generatedAt: new Date().toISOString(),
128
+ mode: 'execute',
129
+ dryRun: false,
130
+ sourceMarket: planPayload && planPayload.sourceMarket ? planPayload.sourceMarket : null,
131
+ planDigest: planPayload && planPayload.planDigest ? planPayload.planDigest : null,
132
+ pandora: {
133
+ marketAddress: reusedManifestLookup.pair.pandoraMarketAddress,
134
+ pollAddress: reusedManifestLookup.pair.pandoraPollAddress || null,
135
+ },
136
+ trustManifest: {
137
+ filePath: reusedManifestLookup.filePath,
138
+ pair: reusedManifestLookup.pair,
139
+ },
140
+ diagnostics: [
141
+ 'Deploy skipped because trusted manifest already maps this source to an on-chain market.',
142
+ ],
143
+ };
144
+ } else {
145
+ if (options.executeLive && typeof assertLiveWriteAllowed === 'function') {
146
+ await assertLiveWriteAllowed('mirror.go.deploy.execute', {
147
+ notionalUsdc: options.liquidityUsdc,
148
+ runtimeMode: 'live',
149
+ });
150
+ }
151
+ deployPayload = await deployMirror({
152
+ ...options,
153
+ planData: planPayload,
154
+ execute: options.executeLive,
155
+ indexerUrl,
156
+ timeoutMs: shared.timeoutMs,
157
+ });
158
+ }
159
+ } catch (err) {
160
+ throw coerceMirrorServiceError(err, 'MIRROR_GO_FAILED');
161
+ }
162
+
163
+ const pandoraMarketAddress = deployPayload && deployPayload.pandora ? deployPayload.pandora.marketAddress : null;
164
+
82
165
  let verifyPayload = null;
83
166
  let syncPayload = null;
84
167
  let polymarketPreflight = null;
85
168
  let suggestedSyncCommand = null;
86
- let trustManifest = deployPayload && deployPayload.trustManifest ? deployPayload.trustManifest : null;
169
+ let trustManifest =
170
+ (deployPayload && deployPayload.trustManifest ? deployPayload.trustManifest : null)
171
+ || (reusedManifestLookup
172
+ ? {
173
+ filePath: reusedManifestLookup.filePath,
174
+ pair: reusedManifestLookup.pair,
175
+ }
176
+ : null);
87
177
  let trustDeploy = Boolean(options.trustDeploy);
88
178
  if (!trustDeploy && trustManifest && trustManifest.pair) {
89
179
  trustDeploy = true;
90
180
  }
91
- const diagnostics = [];
92
- if (deprecatedForceGateWarning) diagnostics.push(deprecatedForceGateWarning);
93
181
 
94
182
  if (pandoraMarketAddress) {
95
183
  if (!trustManifest && (options.executeLive || options.trustDeploy)) {
@@ -127,6 +215,23 @@ module.exports = async function handleMirrorGo({ shared, context, deps, mirrorGo
127
215
  allowRuleMismatch: false,
128
216
  });
129
217
  } catch (err) {
218
+ if (options.executeLive) {
219
+ throw new CliError(
220
+ 'MIRROR_GO_VERIFY_PENDING',
221
+ `Mirror market ${pandoraMarketAddress} is deployed, but verification failed (likely indexer/indexing lag). Do not rerun mirror go --execute-live; run mirror verify against this market address.`,
222
+ {
223
+ pandoraMarketAddress,
224
+ polymarketMarketId: sourceSelector.polymarketMarketId || null,
225
+ polymarketSlug: sourceSelector.polymarketSlug || null,
226
+ manifestFile:
227
+ (trustManifest && trustManifest.filePath)
228
+ || options.manifestFile
229
+ || (typeof defaultMirrorManifestFile === 'function' ? defaultMirrorManifestFile() : null),
230
+ reusedManifestPair: Boolean(reusedManifestLookup),
231
+ cause: err && err.message ? err.message : String(err),
232
+ },
233
+ );
234
+ }
130
235
  throw coerceMirrorServiceError(err, 'MIRROR_GO_VERIFY_FAILED');
131
236
  }
132
237
 
@@ -139,7 +244,8 @@ module.exports = async function handleMirrorGo({ shared, context, deps, mirrorGo
139
244
  if (options.executeLive) {
140
245
  try {
141
246
  polymarketPreflight = await runLivePolymarketPreflightForMirror({
142
- rpcUrl: options.rpcUrl,
247
+ rpcUrl: options.polymarketRpcUrl || options.rpcUrl,
248
+ polymarketRpcUrl: options.polymarketRpcUrl,
143
249
  privateKey: options.privateKey,
144
250
  funder: options.funder,
145
251
  });
@@ -173,6 +279,7 @@ module.exports = async function handleMirrorGo({ shared, context, deps, mirrorGo
173
279
  killSwitchFile: null,
174
280
  chainId: options.chainId,
175
281
  rpcUrl: options.rpcUrl,
282
+ polymarketRpcUrl: options.polymarketRpcUrl,
176
283
  privateKey: options.privateKey,
177
284
  funder: options.funder,
178
285
  usdc: options.usdc,
@@ -248,6 +355,7 @@ module.exports = async function handleMirrorGo({ shared, context, deps, mirrorGo
248
355
  ? `--polymarket-slug ${sourceSelector.polymarketSlug}`
249
356
  : null,
250
357
  '--paper',
358
+ options.polymarketRpcUrl ? `--polymarket-rpc-url ${options.polymarketRpcUrl}` : null,
251
359
  `--drift-trigger-bps ${options.driftTriggerBps}`,
252
360
  `--hedge-trigger-usdc ${options.hedgeTriggerUsdc}`,
253
361
  options.forceGate
@@ -53,6 +53,7 @@ module.exports = async function handleMirrorSync({ shared, context, deps, mirror
53
53
  liveHedgeEnv: [
54
54
  'POLYMARKET_PRIVATE_KEY',
55
55
  'POLYMARKET_FUNDER',
56
+ 'POLYMARKET_RPC_URL',
56
57
  'POLYMARKET_API_KEY',
57
58
  'POLYMARKET_API_SECRET',
58
59
  'POLYMARKET_API_PASSPHRASE',
@@ -75,7 +76,7 @@ module.exports = async function handleMirrorSync({ shared, context, deps, mirror
75
76
  console.log('Daemon stop: pandora mirror sync stop --pid-file <path>|--strategy-hash <hash>');
76
77
  console.log('Daemon status: pandora mirror sync status --pid-file <path>|--strategy-hash <hash>');
77
78
  console.log(
78
- 'Live hedge env: POLYMARKET_PRIVATE_KEY, POLYMARKET_FUNDER, POLYMARKET_API_KEY, POLYMARKET_API_SECRET, POLYMARKET_API_PASSPHRASE, POLYMARKET_HOST.',
79
+ 'Live hedge env: POLYMARKET_PRIVATE_KEY, POLYMARKET_FUNDER, POLYMARKET_RPC_URL, POLYMARKET_API_KEY, POLYMARKET_API_SECRET, POLYMARKET_API_PASSPHRASE, POLYMARKET_HOST.',
79
80
  );
80
81
  console.log('POLYMARKET_FUNDER must be your Polymarket proxy wallet (Gnosis Safe), not your EOA wallet address.');
81
82
  console.log('Polymarket CLOB collateral is Polygon USDC.e; ensure proxy wallet USDC.e balance and approvals are configured.');
@@ -216,7 +217,8 @@ module.exports = async function handleMirrorSync({ shared, context, deps, mirror
216
217
  if (options.executeLive) {
217
218
  try {
218
219
  polymarketPreflight = await runLivePolymarketPreflightForMirror({
219
- rpcUrl: options.rpcUrl,
220
+ rpcUrl: options.polymarketRpcUrl || options.rpcUrl,
221
+ polymarketRpcUrl: options.polymarketRpcUrl,
220
222
  privateKey: options.privateKey,
221
223
  funder: options.funder,
222
224
  });
@@ -270,7 +270,17 @@ async function deployMirror(options = {}) {
270
270
  : Number(planData.distributionHint && planData.distributionHint.distributionNo);
271
271
 
272
272
  const sources = normalizeSources(options.sources);
273
- if (sources.length < 2) {
273
+ if (options.sourcesProvided && sources.length < 2) {
274
+ throw createServiceError(
275
+ 'INVALID_FLAG_VALUE',
276
+ '--sources requires at least two non-empty URLs when explicitly provided.',
277
+ {
278
+ providedCount: Array.isArray(options.sources) ? options.sources.length : 0,
279
+ normalizedCount: sources.length,
280
+ },
281
+ );
282
+ }
283
+ if (!options.sourcesProvided && sources.length < 2) {
274
284
  diagnostics.push('Using fallback source URLs because explicit --sources were not provided.');
275
285
  }
276
286
 
@@ -42,6 +42,7 @@ function createParseLpFlags(deps) {
42
42
  wallet: null,
43
43
  amountUsdc: null,
44
44
  lpTokens: null,
45
+ lpAll: false,
45
46
  chainId: null,
46
47
  dryRun: false,
47
48
  execute: false,
@@ -78,6 +79,10 @@ function createParseLpFlags(deps) {
78
79
  i += 1;
79
80
  continue;
80
81
  }
82
+ if (token === '--all') {
83
+ options.lpAll = true;
84
+ continue;
85
+ }
81
86
  if (token === '--chain-id') {
82
87
  options.chainId = parseInteger(requireFlagValue(rest, i, '--chain-id'), '--chain-id');
83
88
  i += 1;
@@ -172,8 +177,11 @@ function createParseLpFlags(deps) {
172
177
  if (action === 'add' && options.amountUsdc === null) {
173
178
  throw new CliError('MISSING_REQUIRED_FLAG', 'Missing liquidity amount. Use --amount-usdc <amount>.');
174
179
  }
175
- if (action === 'remove' && options.lpTokens === null) {
176
- throw new CliError('MISSING_REQUIRED_FLAG', 'Missing LP token amount. Use --lp-tokens <amount>.');
180
+ if (action === 'remove' && options.lpTokens === null && !options.lpAll) {
181
+ throw new CliError('MISSING_REQUIRED_FLAG', 'Missing LP token amount. Use --lp-tokens <amount> or --all.');
182
+ }
183
+ if (action === 'remove' && options.lpTokens !== null && options.lpAll) {
184
+ throw new CliError('INVALID_ARGS', 'Use only one remove mode: --lp-tokens <amount> or --all.');
177
185
  }
178
186
  if (options.dryRun === options.execute) {
179
187
  throw new CliError('INVALID_ARGS', 'Use exactly one mode: --dry-run or --execute.');
@@ -36,6 +36,7 @@ function createParseMirrorDeployFlags(deps) {
36
36
  arbiter: null,
37
37
  category: 3,
38
38
  sources: [],
39
+ sourcesProvided: false,
39
40
  chainId: null,
40
41
  rpcUrl: null,
41
42
  privateKey: null,
@@ -123,6 +124,7 @@ function createParseMirrorDeployFlags(deps) {
123
124
  if (!entries.length) {
124
125
  throw new CliError('MISSING_FLAG_VALUE', 'Missing value for --sources');
125
126
  }
127
+ options.sourcesProvided = true;
126
128
  options.sources.push(...entries);
127
129
  i = j - 1;
128
130
  continue;
@@ -175,7 +177,14 @@ function createParseMirrorDeployFlags(deps) {
175
177
  continue;
176
178
  }
177
179
  if (token === '--polymarket-host') {
178
- options.polymarketHost = requireFlagValue(args, i, '--polymarket-host');
180
+ const polymarketHost = requireFlagValue(args, i, '--polymarket-host');
181
+ if (!isSecureHttpUrlOrLocal(polymarketHost)) {
182
+ throw new CliError(
183
+ 'INVALID_FLAG_VALUE',
184
+ '--polymarket-host must use https:// (or http://localhost/127.0.0.1 for local testing).',
185
+ );
186
+ }
187
+ options.polymarketHost = polymarketHost;
179
188
  i += 1;
180
189
  continue;
181
190
  }
@@ -49,12 +49,14 @@ function createParseMirrorGoFlags(deps) {
49
49
  cooldownMs: 60_000,
50
50
  chainId: null,
51
51
  rpcUrl: null,
52
+ polymarketRpcUrl: null,
52
53
  privateKey: null,
53
54
  funder: null,
54
55
  usdc: null,
55
56
  oracle: null,
56
57
  factory: null,
57
58
  sources: [],
59
+ sourcesProvided: false,
58
60
  manifestFile: null,
59
61
  trustDeploy: false,
60
62
  forceGate: false,
@@ -200,6 +202,18 @@ function createParseMirrorGoFlags(deps) {
200
202
  i += 1;
201
203
  continue;
202
204
  }
205
+ if (token === '--polymarket-rpc-url') {
206
+ const polymarketRpcUrl = requireFlagValue(args, i, '--polymarket-rpc-url');
207
+ if (!isSecureHttpUrlOrLocal(polymarketRpcUrl)) {
208
+ throw new CliError(
209
+ 'INVALID_FLAG_VALUE',
210
+ '--polymarket-rpc-url must use https:// (or http://localhost/127.0.0.1 for local testing).',
211
+ );
212
+ }
213
+ options.polymarketRpcUrl = polymarketRpcUrl;
214
+ i += 1;
215
+ continue;
216
+ }
203
217
  if (token === '--private-key') {
204
218
  options.privateKey = parsePrivateKeyFlag(requireFlagValue(args, i, '--private-key'), '--private-key');
205
219
  i += 1;
@@ -235,6 +249,7 @@ function createParseMirrorGoFlags(deps) {
235
249
  if (!entries.length) {
236
250
  throw new CliError('MISSING_FLAG_VALUE', 'Missing value for --sources');
237
251
  }
252
+ options.sourcesProvided = true;
238
253
  options.sources.push(...entries);
239
254
  i = j - 1;
240
255
  continue;
@@ -329,11 +344,14 @@ function createParseMirrorGoFlags(deps) {
329
344
  throw new CliError('INVALID_FLAG_VALUE', '--hedge-ratio must be <= 2.');
330
345
  }
331
346
  if (options.executeLive) {
332
- if (options.maxOpenExposureUsdc === null) {
333
- throw new CliError('MISSING_REQUIRED_FLAG', 'Live mode requires --max-open-exposure-usdc.');
334
- }
335
- if (options.maxTradesPerDay === null) {
336
- throw new CliError('MISSING_REQUIRED_FLAG', 'Live mode requires --max-trades-per-day.');
347
+ const missing = [];
348
+ if (options.maxOpenExposureUsdc === null) missing.push('--max-open-exposure-usdc');
349
+ if (options.maxTradesPerDay === null) missing.push('--max-trades-per-day');
350
+ if (missing.length) {
351
+ throw new CliError(
352
+ 'MISSING_REQUIRED_FLAG',
353
+ `Live mode requires companion risk flags: ${missing.join(', ')}.`,
354
+ );
337
355
  }
338
356
  }
339
357
 
@@ -78,6 +78,7 @@ function createParseMirrorSyncFlags(deps) {
78
78
  manifestFile: null,
79
79
  chainId: null,
80
80
  rpcUrl: null,
81
+ polymarketRpcUrl: null,
81
82
  privateKey: null,
82
83
  funder: null,
83
84
  usdc: null,
@@ -256,6 +257,18 @@ function createParseMirrorSyncFlags(deps) {
256
257
  i += 1;
257
258
  continue;
258
259
  }
260
+ if (token === '--polymarket-rpc-url') {
261
+ const polymarketRpcUrl = requireFlagValue(rest, i, '--polymarket-rpc-url');
262
+ if (!isSecureHttpUrlOrLocal(polymarketRpcUrl)) {
263
+ throw new CliError(
264
+ 'INVALID_FLAG_VALUE',
265
+ '--polymarket-rpc-url must use https:// (or http://localhost/127.0.0.1 for local testing).',
266
+ );
267
+ }
268
+ options.polymarketRpcUrl = polymarketRpcUrl;
269
+ i += 1;
270
+ continue;
271
+ }
259
272
  if (token === '--private-key') {
260
273
  options.privateKey = parsePrivateKeyFlag(requireFlagValue(rest, i, '--private-key'), '--private-key');
261
274
  i += 1;
@@ -356,11 +369,14 @@ function createParseMirrorSyncFlags(deps) {
356
369
  }
357
370
 
358
371
  if (options.executeLive) {
359
- if (options.maxOpenExposureUsdc === null) {
360
- throw new CliError('MISSING_REQUIRED_FLAG', 'Live mode requires --max-open-exposure-usdc.');
361
- }
362
- if (options.maxTradesPerDay === null) {
363
- throw new CliError('MISSING_REQUIRED_FLAG', 'Live mode requires --max-trades-per-day.');
372
+ const missing = [];
373
+ if (options.maxOpenExposureUsdc === null) missing.push('--max-open-exposure-usdc');
374
+ if (options.maxTradesPerDay === null) missing.push('--max-trades-per-day');
375
+ if (missing.length) {
376
+ throw new CliError(
377
+ 'MISSING_REQUIRED_FLAG',
378
+ `Live mode requires companion risk flags: ${missing.join(', ')}.`,
379
+ );
364
380
  }
365
381
  } else {
366
382
  if (options.maxOpenExposureUsdc === null) options.maxOpenExposureUsdc = Number.POSITIVE_INFINITY;