pandora-cli-skills 1.1.48 → 1.1.50
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/README_FOR_SHARING.md +1 -1
- package/SKILL.md +3 -3
- package/cli/lib/contract_error_decoder.cjs +9 -0
- package/cli/lib/error_recovery_service.cjs +25 -1
- package/cli/lib/lp_command_service.cjs +13 -3
- package/cli/lib/market_admin_service.cjs +35 -2
- package/cli/lib/mirror_command_service.cjs +2 -2
- package/cli/lib/mirror_handlers/go.cjs +127 -19
- package/cli/lib/mirror_handlers/sync.cjs +4 -2
- package/cli/lib/mirror_service.cjs +11 -1
- package/cli/lib/parsers/lp_flags.cjs +10 -2
- package/cli/lib/parsers/mirror_deploy_flags.cjs +28 -1
- package/cli/lib/parsers/mirror_go_flags.cjs +41 -5
- package/cli/lib/parsers/mirror_sync_flags.cjs +21 -5
- package/cli/lib/polymarket_ops_service.cjs +29 -0
- package/cli/lib/resolve_command_service.cjs +8 -4
- package/cli/pandora.cjs +46 -5
- package/package.json +1 -1
- package/tests/cli/cli.integration.test.cjs +23 -0
- package/tests/unit/lp_command_service.test.cjs +125 -0
- package/tests/unit/lp_flags.test.cjs +92 -0
- package/tests/unit/mirror_go_regressions.test.cjs +479 -0
- package/tests/unit/new-features.test.cjs +76 -0
package/README_FOR_SHARING.md
CHANGED
|
@@ -367,7 +367,7 @@ Mirror advanced flags (for operator tuning):
|
|
|
367
367
|
|
|
368
368
|
### Resolve command
|
|
369
369
|
- Usage:
|
|
370
|
-
- `pandora [--output table|json] resolve --poll-address <address> --answer yes|no --reason <text> --dry-run|--execute [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]`
|
|
370
|
+
- `pandora [--output table|json] resolve [--dotenv-path <path>] [--skip-dotenv] --poll-address <address> --answer yes|no|invalid --reason <text> --dry-run|--execute [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]`
|
|
371
371
|
- Behavior:
|
|
372
372
|
- `--dry-run` returns a deterministic execution plan.
|
|
373
373
|
- `--execute` submits the resolution transaction with decoded revert diagnostics on failure.
|
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.
|
|
4
|
+
version: 1.1.50
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Pandora CLI & Skills
|
|
@@ -145,7 +145,7 @@ pandora [--output table|json] webhook test [--webhook-url <url>] [--webhook-temp
|
|
|
145
145
|
pandora [--output table|json] leaderboard [--metric profit|volume|win-rate] [--chain-id <id>] [--limit <n>] [--min-trades <n>]
|
|
146
146
|
pandora [--output table|json] analyze --market-address <address> [--provider <name>] [--model <id>] [--max-cost-usd <n>] [--temperature <n>] [--timeout-ms <ms>]
|
|
147
147
|
pandora [--output table|json] suggest --wallet <address> --risk low|medium|high --budget <amount> [--count <n>] [--include-venues pandora,polymarket]
|
|
148
|
-
pandora [--output table|json] resolve --poll-address <address> --answer yes|no|invalid --reason <text> --dry-run|--execute [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]
|
|
148
|
+
pandora [--output table|json] resolve [--dotenv-path <path>] [--skip-dotenv] --poll-address <address> --answer yes|no|invalid --reason <text> --dry-run|--execute [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]
|
|
149
149
|
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>]
|
|
150
150
|
pandora [--output table|json] risk show|panic [--risk-file <path>] [--clear] [--reason <text>] [--actor <id>]
|
|
151
151
|
pandora stream prices|events [--indexer-url <url>] [--indexer-ws-url <url>] [--timeout-ms <ms>] [--interval-ms <ms>] [--market-address <address>] [--chain-id <id>] [--limit <n>]
|
|
@@ -418,7 +418,7 @@ pandora --output json schema
|
|
|
418
418
|
|
|
419
419
|
### Resolve command
|
|
420
420
|
- Usage:
|
|
421
|
-
- `pandora [--output table|json] resolve --poll-address <address> --answer yes|no --reason <text> --dry-run|--execute [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]`
|
|
421
|
+
- `pandora [--output table|json] resolve [--dotenv-path <path>] [--skip-dotenv] --poll-address <address> --answer yes|no|invalid --reason <text> --dry-run|--execute [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]`
|
|
422
422
|
- Behavior:
|
|
423
423
|
- `--dry-run` returns the call plan and decode-ready payload.
|
|
424
424
|
- `--execute` submits on-chain resolution through configured oracle/operator path.
|
|
@@ -35,6 +35,11 @@ const CONTRACT_ERROR_ABI = [
|
|
|
35
35
|
},
|
|
36
36
|
];
|
|
37
37
|
|
|
38
|
+
const REVERT_SELECTOR_HINTS = {
|
|
39
|
+
// Market-specific minimum-notional guard seen on some Pandora AMM deployments.
|
|
40
|
+
'0x7e2d7787': 'Trade too small for this market. Increase --amount-usdc and retry.',
|
|
41
|
+
};
|
|
42
|
+
|
|
38
43
|
function isHexData(value) {
|
|
39
44
|
return /^0x[0-9a-fA-F]*$/.test(String(value || ''));
|
|
40
45
|
}
|
|
@@ -126,6 +131,10 @@ function formatDecodedContractError(decoded) {
|
|
|
126
131
|
return decoded.errorName;
|
|
127
132
|
}
|
|
128
133
|
if (decoded.data) {
|
|
134
|
+
const selector = String(decoded.data).slice(0, 10).toLowerCase();
|
|
135
|
+
if (REVERT_SELECTOR_HINTS[selector]) {
|
|
136
|
+
return `${REVERT_SELECTOR_HINTS[selector]} (selector ${selector})`;
|
|
137
|
+
}
|
|
129
138
|
return `Contract reverted (${decoded.data})`;
|
|
130
139
|
}
|
|
131
140
|
return null;
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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.');
|
|
@@ -7,6 +7,18 @@ function requireDep(deps, name) {
|
|
|
7
7
|
return deps[name];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
function normalizeSources(entries) {
|
|
11
|
+
const values = [];
|
|
12
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
13
|
+
const parts = String(entry || '').split(/[\n,]/g);
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
const normalized = String(part || '').trim();
|
|
16
|
+
if (normalized) values.push(normalized);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return values;
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
/**
|
|
11
23
|
* Creates the mirror deploy flags parser.
|
|
12
24
|
* @param {object} deps
|
|
@@ -36,6 +48,7 @@ function createParseMirrorDeployFlags(deps) {
|
|
|
36
48
|
arbiter: null,
|
|
37
49
|
category: 3,
|
|
38
50
|
sources: [],
|
|
51
|
+
sourcesProvided: false,
|
|
39
52
|
chainId: null,
|
|
40
53
|
rpcUrl: null,
|
|
41
54
|
privateKey: null,
|
|
@@ -123,6 +136,7 @@ function createParseMirrorDeployFlags(deps) {
|
|
|
123
136
|
if (!entries.length) {
|
|
124
137
|
throw new CliError('MISSING_FLAG_VALUE', 'Missing value for --sources');
|
|
125
138
|
}
|
|
139
|
+
options.sourcesProvided = true;
|
|
126
140
|
options.sources.push(...entries);
|
|
127
141
|
i = j - 1;
|
|
128
142
|
continue;
|
|
@@ -175,7 +189,14 @@ function createParseMirrorDeployFlags(deps) {
|
|
|
175
189
|
continue;
|
|
176
190
|
}
|
|
177
191
|
if (token === '--polymarket-host') {
|
|
178
|
-
|
|
192
|
+
const polymarketHost = requireFlagValue(args, i, '--polymarket-host');
|
|
193
|
+
if (!isSecureHttpUrlOrLocal(polymarketHost)) {
|
|
194
|
+
throw new CliError(
|
|
195
|
+
'INVALID_FLAG_VALUE',
|
|
196
|
+
'--polymarket-host must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
options.polymarketHost = polymarketHost;
|
|
179
200
|
i += 1;
|
|
180
201
|
continue;
|
|
181
202
|
}
|
|
@@ -241,6 +262,12 @@ function createParseMirrorDeployFlags(deps) {
|
|
|
241
262
|
) {
|
|
242
263
|
throw new CliError('INVALID_ARGS', '--distribution-yes + --distribution-no must equal 1000000000.');
|
|
243
264
|
}
|
|
265
|
+
if (options.sourcesProvided && normalizeSources(options.sources).length < 2) {
|
|
266
|
+
throw new CliError(
|
|
267
|
+
'INVALID_FLAG_VALUE',
|
|
268
|
+
'--sources requires at least two non-empty URLs when explicitly provided.',
|
|
269
|
+
);
|
|
270
|
+
}
|
|
244
271
|
|
|
245
272
|
return options;
|
|
246
273
|
};
|