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 +1 -1
- 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 +10 -1
- package/cli/lib/parsers/mirror_go_flags.cjs +23 -5
- package/cli/lib/parsers/mirror_sync_flags.cjs +21 -5
- package/cli/lib/polymarket_ops_service.cjs +29 -0
- package/cli/pandora.cjs +38 -2
- package/package.json +1 -1
- 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 +1 -0
package/SKILL.md
CHANGED
|
@@ -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.');
|
|
@@ -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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
throw new CliError(
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (
|
|
363
|
-
throw new CliError(
|
|
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;
|