@hedge-layer/cli 1.5.0 → 1.7.0

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/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command2 } from "commander";
4
+ import { Command as Command4 } from "commander";
5
5
 
6
6
  // src/commands/auth.ts
7
7
  import readline from "readline/promises";
@@ -300,7 +300,7 @@ function registerAuthCommands(program2) {
300
300
  try {
301
301
  heading("Hedge Layer CLI \u2014 Login");
302
302
  process.stderr.write(
303
- `Create an API token at ${bold("https://hedgelayer.ai/settings")} \u2192 API Tokens
303
+ `Create an API token at ${bold("https://hedgelayer.ai/account/settings")} \u2192 API Tokens
304
304
 
305
305
  `
306
306
  );
@@ -1184,6 +1184,13 @@ function parseNonNegative(value) {
1184
1184
  }
1185
1185
  return n;
1186
1186
  }
1187
+ function parsePositiveNumber(value) {
1188
+ const n = Number(value);
1189
+ if (!Number.isFinite(n) || n <= 0) {
1190
+ throw new InvalidArgumentError("Expected a positive number");
1191
+ }
1192
+ return n;
1193
+ }
1187
1194
  function parsePositiveInt(value) {
1188
1195
  const n = Number(value);
1189
1196
  if (!Number.isInteger(n) || n < 1) {
@@ -1215,6 +1222,9 @@ function strategyFromOptions(opts) {
1215
1222
  id: "cli-dry-run",
1216
1223
  name: "CLI dry-run LP strategy",
1217
1224
  status: opts.paused ? "paused" : "dry_run",
1225
+ ...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
1226
+ ...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
1227
+ ...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
1218
1228
  capital_limit: opts.capitalLimit,
1219
1229
  per_market_limit: opts.perMarketLimit,
1220
1230
  min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
@@ -1227,6 +1237,15 @@ function strategyFromOptions(opts) {
1227
1237
  max_markets: opts.maxMarkets
1228
1238
  };
1229
1239
  }
1240
+ function validatePercentageSizing(opts) {
1241
+ const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
1242
+ if (usesPercentageSizing && opts.totalHoldings === void 0) {
1243
+ error(
1244
+ "Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
1245
+ );
1246
+ process.exit(1);
1247
+ }
1248
+ }
1230
1249
  async function readAllocations(path) {
1231
1250
  if (!path) return [];
1232
1251
  const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
@@ -1252,10 +1271,11 @@ function registerAllocatorCommands(program2) {
1252
1271
  "[screening]",
1253
1272
  `Optional screening preset: ${PROFILE_CHOICES2.join(" | ")} (same as --profile)`,
1254
1273
  "lp-opportunity"
1255
- ).option("--profile <name>", `Screening defaults: ${PROFILE_CHOICES2.join(", ")}`).option("--sort-by <key>", "score | volume | liquidity | movement | spread | rewards | rewardYield | lpExpectedReturn | horizon").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)").option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)").option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)").option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day").option("--min-days-to-end <n>", "Feed filter: minimum days until resolution").option("--max-days-to-end <n>", "Feed filter: maximum days until resolution").option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch").option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max feed markets to fetch (1-100, default 15)", "15").option("--capital-limit <usd>", "Portfolio capital limit for this cycle", parseNonNegative, 500).option("--per-market-limit <usd>", "Per-market target cap", parseNonNegative, 100).option("--min-expected-return-daily-pct <pct>", "Minimum expected daily return percent", parseNonNegative, 0.02).option("--max-inventory-imbalance <ratio>", "Maximum inventory imbalance", parseNonNegative, 0.25).option("--max-order-notional <usd>", "Maximum notional per planned passive order", parseNonNegative, 25).option("--quote-edge-bps <bps>", "Passive quote edge in basis points", parseNonNegative, 100).option("--allocator-min-liquidity <usd>", "Allocator safety gate: minimum market liquidity", parseNonNegative, 500).option("--max-spread <ratio>", "Allocator safety gate: maximum spread", parseNonNegative, 0.12).option("--allocator-min-days-to-end <n>", "Allocator safety gate: minimum days to resolution", parseNonNegative, 3).option("--max-markets <n>", "Maximum markets allocator may target", parsePositiveInt, 5).option("--allocations <file>", "Existing allocations JSON array; use '-' to read stdin").option("--repeat", "Run a second cycle using targets returned by the first cycle").option("--paused", "Send strategy status paused instead of dry_run").action(async (screening, o) => {
1274
+ ).option("--profile <name>", `Screening defaults: ${PROFILE_CHOICES2.join(", ")}`).option("--sort-by <key>", "score | volume | liquidity | movement | spread | rewards | rewardYield | lpExpectedReturn | horizon").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)").option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)").option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)").option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day").option("--min-days-to-end <n>", "Feed filter: minimum days until resolution").option("--max-days-to-end <n>", "Feed filter: maximum days until resolution").option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch").option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max feed markets to fetch (1-100, default 15)", "15").option("--total-holdings <usd>", "Total user holdings / portfolio value used for percentage sizing", parsePositiveNumber).option("--capital-limit-pct <pct>", "Portfolio-level allocation cap as a percent of total holdings", parsePositiveNumber).option("--per-market-limit-pct <pct>", "Per-market target cap as a percent of total holdings", parsePositiveNumber).option("--capital-limit <usd>", "Portfolio capital limit for this cycle", parseNonNegative, 500).option("--per-market-limit <usd>", "Per-market target cap", parseNonNegative, 100).option("--min-expected-return-daily-pct <pct>", "Minimum expected daily return percent", parseNonNegative, 0.02).option("--max-inventory-imbalance <ratio>", "Maximum inventory imbalance", parseNonNegative, 0.25).option("--max-order-notional <usd>", "Maximum notional per planned passive order", parseNonNegative, 25).option("--quote-edge-bps <bps>", "Passive quote edge in basis points", parseNonNegative, 100).option("--allocator-min-liquidity <usd>", "Allocator safety gate: minimum market liquidity", parseNonNegative, 500).option("--max-spread <ratio>", "Allocator safety gate: maximum spread", parseNonNegative, 0.12).option("--allocator-min-days-to-end <n>", "Allocator safety gate: minimum days to resolution", parseNonNegative, 3).option("--max-markets <n>", "Maximum markets allocator may target", parsePositiveInt, 5).option("--allocations <file>", "Existing allocations JSON array; use '-' to read stdin").option("--repeat", "Run a second cycle using targets returned by the first cycle").option("--paused", "Send strategy status paused instead of dry_run").action(async (screening, o) => {
1256
1275
  const globalOpts = program2.opts();
1257
1276
  const client = new ApiClient(globalOpts);
1258
1277
  requireAuth5(client);
1278
+ validatePercentageSizing(o);
1259
1279
  let profile = o.profile ?? screening ?? "lp-opportunity";
1260
1280
  if (!isProfile2(profile)) {
1261
1281
  error(`Unknown screening "${profile}". Use: ${PROFILE_CHOICES2.join(" or ")}`);
@@ -1313,14 +1333,562 @@ function registerAllocatorCommands(program2) {
1313
1333
  });
1314
1334
  }
1315
1335
 
1336
+ // src/commands/lp.ts
1337
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1338
+ import { writeFile } from "fs/promises";
1339
+
1340
+ // src/lp-display.ts
1341
+ import chalk7 from "chalk";
1342
+ function num2(value, fallback = 0) {
1343
+ const n = Number(value);
1344
+ return Number.isFinite(n) ? n : fallback;
1345
+ }
1346
+ function signedCurrency2(value) {
1347
+ const formatted = currency(Math.abs(value));
1348
+ if (value > 0) return chalk7.green(`+${formatted}`);
1349
+ if (value < 0) return chalk7.yellow(`-${formatted}`);
1350
+ return chalk7.dim("$0.00");
1351
+ }
1352
+ function actionSummary(actions) {
1353
+ if (!actions) return "none";
1354
+ return Object.entries(actions).filter(([, count]) => num2(count) > 0).map(([action, count]) => `${action}:${num2(count)}`).join(" ");
1355
+ }
1356
+ function displayLpScanResult(result, globalOpts) {
1357
+ if (globalOpts.json) {
1358
+ json(result);
1359
+ return;
1360
+ }
1361
+ heading("LP Scan");
1362
+ process.stdout.write(
1363
+ chalk7.dim(
1364
+ ` scan ${result.scanId} \xB7 strategy ${result.strategyId} \xB7 ${result.evidenceSaved} evidence rows saved
1365
+ `
1366
+ )
1367
+ );
1368
+ process.stdout.write(
1369
+ chalk7.dim(
1370
+ ` ${result.totalScanned.toLocaleString()} scanned \xB7 ${result.totalAfterFilter.toLocaleString()} after filters \xB7 profile ${result.profile}
1371
+
1372
+ `
1373
+ )
1374
+ );
1375
+ if (result.markets.length === 0) {
1376
+ warn("No markets matched the LP scan.");
1377
+ return;
1378
+ }
1379
+ table(
1380
+ result.markets.slice(0, 10).map((market) => [
1381
+ String(market.rank),
1382
+ truncate(market.question, 46),
1383
+ String(Math.round(num2(market.score))),
1384
+ compactCurrency(num2(market.liquidity)),
1385
+ compactCurrency(num2(market.rewardsDailyRate)) + "/day",
1386
+ `${num2(market.lpExpectedReturnDailyPct).toFixed(3)}%`
1387
+ ]),
1388
+ ["#", "Market", "Score", "Liq", "Rewards", "Exp/day"]
1389
+ );
1390
+ }
1391
+ function displayLpRecommendResult(result, globalOpts) {
1392
+ if (globalOpts.json) {
1393
+ json(result);
1394
+ return;
1395
+ }
1396
+ heading("LP Recommendations");
1397
+ process.stdout.write(
1398
+ chalk7.dim(
1399
+ ` cycle ${result.cycleId} \xB7 strategy ${result.strategyId} \xB7 ${result.candidatesSubmitted} markets \xB7 ${result.allocationsSubmitted} current allocations
1400
+ `
1401
+ )
1402
+ );
1403
+ process.stdout.write(
1404
+ chalk7.dim(
1405
+ ` PnL context ${result.pnlContextCount} rows${result.pnlSynced ? " \xB7 synced" : ""} \xB7 approvals required
1406
+
1407
+ `
1408
+ )
1409
+ );
1410
+ displayAllocatorCycleResult(result.result ?? { decisions: result.decisions }, globalOpts);
1411
+ }
1412
+ function displayLpEvaluateResult(result, globalOpts) {
1413
+ if (globalOpts.json) {
1414
+ json(result);
1415
+ return;
1416
+ }
1417
+ heading("LP Evaluation");
1418
+ process.stdout.write(
1419
+ chalk7.dim(
1420
+ ` strategy ${result.strategyId} \xB7 ${result.summary.snapshots} snapshots \xB7 ${result.summary.markets} markets`
1421
+ )
1422
+ );
1423
+ if (result.pnlSynced) process.stdout.write(chalk7.dim(" \xB7 synced"));
1424
+ process.stdout.write("\n\n");
1425
+ if (result.syncError) warn(result.syncError);
1426
+ table(
1427
+ [
1428
+ ["Realized PnL", signedCurrency2(result.summary.realizedPnl)],
1429
+ ["Unrealized PnL", signedCurrency2(result.summary.unrealizedPnl)],
1430
+ ["Net PnL", signedCurrency2(result.summary.netPnl)],
1431
+ ["Capital locked", currency(result.summary.capitalLocked)],
1432
+ ["Current value", currency(result.summary.currentValue)],
1433
+ ["Outcomes", actionSummary(result.summary.outcomes)]
1434
+ ]
1435
+ );
1436
+ if (result.lessons.length === 0) {
1437
+ warn("No PnL lessons available yet.");
1438
+ return;
1439
+ }
1440
+ process.stdout.write("\n");
1441
+ table(
1442
+ result.lessons.slice(0, 8).map((lesson) => [
1443
+ truncate(String(lesson.market_slug ?? "portfolio"), 26),
1444
+ String(lesson.outcome ?? "flat"),
1445
+ signedCurrency2(num2(lesson.net_pnl)),
1446
+ truncate(String(lesson.lesson ?? "no lesson"), 64)
1447
+ ]),
1448
+ ["Market", "Outcome", "Net", "Lesson"]
1449
+ );
1450
+ }
1451
+ function displayLpRunResult(result, globalOpts) {
1452
+ if (globalOpts.json) {
1453
+ json(result);
1454
+ return;
1455
+ }
1456
+ heading("LP Run");
1457
+ process.stdout.write(
1458
+ chalk7.dim(
1459
+ ` cycle ${result.run.cycleId} \xB7 scan ${result.run.scanId ?? "n/a"} \xB7 strategy ${result.run.strategyId}
1460
+ `
1461
+ )
1462
+ );
1463
+ process.stdout.write(
1464
+ chalk7.dim(
1465
+ ` ${result.run.opportunitiesFound} opportunities \xB7 PnL ${result.run.pnlSynced ? "synced" : "not synced"} \xB7 approvals required
1466
+
1467
+ `
1468
+ )
1469
+ );
1470
+ displayAllocatorCycleResult(result.run.result, globalOpts);
1471
+ if (result.evaluation) {
1472
+ process.stdout.write("\n");
1473
+ process.stdout.write(
1474
+ chalk7.dim(
1475
+ ` Evaluation: ${signedCurrency2(result.evaluation.summary.netPnl)} net PnL across ${result.evaluation.summary.markets} markets
1476
+ `
1477
+ )
1478
+ );
1479
+ }
1480
+ }
1481
+
1482
+ // src/commands/lp.ts
1483
+ function requireAuth6(client) {
1484
+ if (!client.isAuthenticated) {
1485
+ error("Not authenticated. Run `hl auth login` first.");
1486
+ process.exit(1);
1487
+ }
1488
+ }
1489
+ function parseNonNegative2(value) {
1490
+ const n = Number(value);
1491
+ if (!Number.isFinite(n) || n < 0) {
1492
+ throw new InvalidArgumentError2("Expected a non-negative number");
1493
+ }
1494
+ return n;
1495
+ }
1496
+ function parsePositiveInt2(value) {
1497
+ const n = Number(value);
1498
+ if (!Number.isInteger(n) || n < 1) {
1499
+ throw new InvalidArgumentError2("Expected a positive integer");
1500
+ }
1501
+ return n;
1502
+ }
1503
+ function compact(payload) {
1504
+ return Object.fromEntries(
1505
+ Object.entries(payload).filter(([, value]) => value !== void 0 && value !== "")
1506
+ );
1507
+ }
1508
+ async function writeArtifact(path, data, jsonMode) {
1509
+ if (!path) return;
1510
+ await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf8");
1511
+ if (!jsonMode) {
1512
+ process.stderr.write(dim(` Saved artifact to ${path}
1513
+ `));
1514
+ }
1515
+ }
1516
+ function buildLpScanPayload(topic, opts) {
1517
+ return compact({
1518
+ topic: topic?.trim() || void 0,
1519
+ strategyId: opts.strategyId,
1520
+ profile: opts.profile,
1521
+ sortBy: opts.sortBy,
1522
+ tag: opts.tag,
1523
+ minVolume: opts.minVolume,
1524
+ minLiquidity: opts.minLiquidity,
1525
+ maxLiquidity: opts.maxLiquidity,
1526
+ minRewardsDailyRate: opts.minRewardsDailyRate,
1527
+ minDaysToEnd: opts.minDaysToEnd,
1528
+ maxDaysToEnd: opts.maxDaysToEnd,
1529
+ maxMarketAgeHours: opts.maxMarketAgeHours,
1530
+ liquidProfile: opts.liquidProfile,
1531
+ limit: opts.limit
1532
+ });
1533
+ }
1534
+ function buildLpRecommendPayload(opts) {
1535
+ return compact({
1536
+ strategyId: opts.strategyId,
1537
+ scanId: opts.scanId,
1538
+ limit: opts.limit,
1539
+ syncPnl: opts.syncPnl
1540
+ });
1541
+ }
1542
+ function buildLpEvaluatePayload(opts) {
1543
+ return compact({
1544
+ strategyId: opts.strategyId,
1545
+ walletAddress: opts.walletAddress,
1546
+ syncPnl: opts.syncPnl,
1547
+ limit: opts.limit
1548
+ });
1549
+ }
1550
+ function buildLpRunPayload(opts) {
1551
+ return compact({
1552
+ strategyId: opts.strategyId,
1553
+ limit: opts.limit,
1554
+ syncPnl: opts.syncPnl
1555
+ });
1556
+ }
1557
+ function registerLpCommands(program2) {
1558
+ const lp = program2.command("lp").description("Run persisted liquidity-provider scan, recommendation, and evaluation workflows");
1559
+ lp.command("scan").description("Scan LP candidates and persist them as evidence").argument("[topic]", "Human label for this scan, e.g. liquidity opportunities").option("--profile <name>", "lp-opportunity | liquidity-provider | liquid-new-or-long", "liquidity-provider").option("--sort-by <key>", "score | volume | liquidity | movement | spread | rewards | rewardYield | lpExpectedReturn | horizon").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)", parseNonNegative2).option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)", parseNonNegative2).option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)", parseNonNegative2).option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day", parseNonNegative2).option("--min-days-to-end <n>", "Minimum days until resolution", parseNonNegative2).option("--max-days-to-end <n>", "Maximum days until resolution", parseNonNegative2).option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch", parseNonNegative2).option("--liquid-profile <mode>", "new-or-long").option("--limit <n>", "Max markets to scan/save (1-100, default 15)", parsePositiveInt2, 15).option("--strategy-id <uuid>", "LP strategy id").option("--output <file>", "Write the evidence JSON response to a local file").action(async (topic, opts) => {
1560
+ const globalOpts = program2.opts();
1561
+ const client = new ApiClient(globalOpts);
1562
+ requireAuth6(client);
1563
+ const result = await client.post(
1564
+ "/api/lp/scan",
1565
+ buildLpScanPayload(topic, opts)
1566
+ );
1567
+ await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1568
+ displayLpScanResult(result, globalOpts);
1569
+ });
1570
+ lp.command("recommend").description("Run allocator recommendations from saved LP evidence and current allocations").option("--strategy-id <uuid>", "LP strategy id").option("--scan-id <uuid>", "Use candidates from a specific saved scan").option("--limit <n>", "Max saved candidates to submit (1-25, default 15)", parsePositiveInt2, 15).option("--sync-pnl", "Refresh wallet PnL before recommending").option("--output <file>", "Write the recommendation JSON response to a local file").action(async (opts) => {
1571
+ const globalOpts = program2.opts();
1572
+ const client = new ApiClient(globalOpts);
1573
+ requireAuth6(client);
1574
+ const result = await client.post(
1575
+ "/api/lp/recommend",
1576
+ buildLpRecommendPayload(opts)
1577
+ );
1578
+ await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1579
+ displayLpRecommendResult(result, globalOpts);
1580
+ });
1581
+ lp.command("evaluate").description("Evaluate LP performance and return compact lessons").option("--strategy-id <uuid>", "LP strategy id").option("--wallet-address <address>", "Wallet address to sync PnL from").option("--no-sync-pnl", "Use existing PnL snapshots without refreshing").option("--limit <n>", "Max PnL snapshots to evaluate (1-100, default 50)", parsePositiveInt2, 50).option("--output <file>", "Write the evaluation JSON response to a local file").action(async (opts) => {
1582
+ const globalOpts = program2.opts();
1583
+ const client = new ApiClient(globalOpts);
1584
+ requireAuth6(client);
1585
+ const result = await client.post(
1586
+ "/api/lp/evaluate",
1587
+ buildLpEvaluatePayload(opts)
1588
+ );
1589
+ await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1590
+ displayLpEvaluateResult(result, globalOpts);
1591
+ });
1592
+ lp.command("run").description("Run scan, recommendation, and evaluation as one dry-run chain").option("--strategy-id <uuid>", "LP strategy id").option("--limit <n>", "Max candidates to scan/recommend (1-25, default 15)", parsePositiveInt2, 15).option("--no-sync-pnl", "Use existing PnL snapshots without refreshing").option("--output <file>", "Write the chained run JSON response to a local file").action(async (opts) => {
1593
+ const globalOpts = program2.opts();
1594
+ const client = new ApiClient(globalOpts);
1595
+ requireAuth6(client);
1596
+ const run = await client.post(
1597
+ "/api/lp/run",
1598
+ buildLpRunPayload(opts)
1599
+ );
1600
+ let evaluation = null;
1601
+ try {
1602
+ evaluation = await client.post("/api/lp/evaluate", {
1603
+ strategyId: run.strategyId,
1604
+ syncPnl: false
1605
+ });
1606
+ } catch (error2) {
1607
+ if (!globalOpts.json) {
1608
+ warn(
1609
+ `Could not load evaluation summary: ${error2 instanceof Error ? error2.message : String(error2)}`
1610
+ );
1611
+ }
1612
+ }
1613
+ const combined = { run, evaluation };
1614
+ await writeArtifact(opts.output, combined, Boolean(globalOpts.json));
1615
+ displayLpRunResult(combined, globalOpts);
1616
+ });
1617
+ }
1618
+
1619
+ // src/commands/wallet.ts
1620
+ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
1621
+ import { spawn } from "child_process";
1622
+ var NETWORK_NAME = "Polygon";
1623
+ var POLYGON_NATIVE_USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
1624
+ var SUPPORTED_ASSETS = ["pUsd", "usdcE", "matic"];
1625
+ var POLYGON_NATIVE_ASSET_LABEL = "POL";
1626
+ var TERMINAL_WITHDRAWAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "expired"]);
1627
+ function requireAuth7(client) {
1628
+ if (!client.isAuthenticated) {
1629
+ error("Not authenticated. Run `hl auth login` first.");
1630
+ process.exit(1);
1631
+ }
1632
+ }
1633
+ function parsePositiveNumber2(value) {
1634
+ const n = Number(value);
1635
+ if (!Number.isFinite(n) || n <= 0) {
1636
+ throw new InvalidArgumentError3("Expected a positive number");
1637
+ }
1638
+ return n;
1639
+ }
1640
+ function parseWalletAddress(value) {
1641
+ const trimmed = value.trim();
1642
+ if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) {
1643
+ throw new InvalidArgumentError3("Expected a valid EVM wallet address");
1644
+ }
1645
+ return trimmed;
1646
+ }
1647
+ function normalizeWalletAsset(value) {
1648
+ if (!value) return void 0;
1649
+ const normalized = value.trim().toLowerCase().replace(/[._\s-]/g, "");
1650
+ if (["pusd", "polymarketusd"].includes(normalized)) return "pUsd";
1651
+ if (["usdce", "usdcebridged", "bridgedusdc", "usdc"].includes(normalized)) return "usdcE";
1652
+ if (["matic", "pol"].includes(normalized)) return "matic";
1653
+ throw new InvalidArgumentError3("Unknown wallet asset. Use pUSD, USDC.e, or POL.");
1654
+ }
1655
+ function assetByKey(balances, key) {
1656
+ return balances.assets[key];
1657
+ }
1658
+ function tokenAddress(asset) {
1659
+ return asset.address ?? "native token";
1660
+ }
1661
+ function displayAmount(asset) {
1662
+ if (!asset.ok) {
1663
+ return `unavailable${asset.error ? ` (${asset.error})` : ""}`;
1664
+ }
1665
+ const numeric = Number(asset.balance);
1666
+ if (asset.address === null && asset.decimals === 18) {
1667
+ return Number.isFinite(numeric) ? numeric.toLocaleString("en-US", { maximumFractionDigits: 6 }) : asset.balance;
1668
+ }
1669
+ return Number.isFinite(numeric) ? currency(numeric) : asset.balance;
1670
+ }
1671
+ function displayAssetSymbol(asset) {
1672
+ return asset.address === null && asset.decimals === 18 ? POLYGON_NATIVE_ASSET_LABEL : asset.symbol;
1673
+ }
1674
+ function buildDepositInstructions(balances, assetKey) {
1675
+ const assets = assetKey ? [assetByKey(balances, assetKey)] : SUPPORTED_ASSETS.map((key) => assetByKey(balances, key));
1676
+ return {
1677
+ action: "deposit",
1678
+ publicDepositAddress: balances.walletAddress,
1679
+ walletAddress: balances.walletAddress,
1680
+ chainId: balances.chainId,
1681
+ network: NETWORK_NAME,
1682
+ asset: assetKey ? assetByKey(balances, assetKey) : null,
1683
+ assets,
1684
+ warning: "Send only Polygon assets to this address. Hedge Layer cannot reverse external transfers."
1685
+ };
1686
+ }
1687
+ function buildWithdrawRequestPayload(opts) {
1688
+ const assetKey = normalizeWalletAsset(opts.asset) ?? "pUsd";
1689
+ if (assetKey !== "pUsd") {
1690
+ throw new InvalidArgumentError3("Bridge withdrawals currently send pUSD. Use --asset pUSD.");
1691
+ }
1692
+ return {
1693
+ amount: opts.amount,
1694
+ recipientAddress: opts.to,
1695
+ toChainId: opts.toChainId ?? "137",
1696
+ toTokenAddress: opts.toTokenAddress ?? POLYGON_NATIVE_USDC
1697
+ };
1698
+ }
1699
+ function displayWalletStatus(status) {
1700
+ const ownerWallet = status.ownerWallet ?? status.wallet;
1701
+ const depositWallet = status.depositWallet ?? status.tradingWallet ?? null;
1702
+ heading("Wallet");
1703
+ table([
1704
+ ["Owner linked", status.linked ? "yes" : "no"],
1705
+ ["Provider", status.provider],
1706
+ ["Owner wallet", ownerWallet?.wallet_address ?? "(none)"],
1707
+ ["Owner chain", ownerWallet ? `${NETWORK_NAME} (${ownerWallet.chain_id})` : "(none)"],
1708
+ ["Owner linked at", ownerWallet?.linked_at ? new Date(ownerWallet.linked_at).toLocaleString() : "(none)"],
1709
+ ["Deposit wallet", depositWallet?.wallet_address ?? "(none)"],
1710
+ ["Deposit deployed", status.depositWalletDeployed ? "yes" : "no"],
1711
+ ["Deposit approved", status.depositWalletApproved ? "yes" : "no"],
1712
+ ["Deposit ready", status.depositWalletReady ? "yes" : "no"],
1713
+ ["Relayer configured", status.relayerConfigured ? "yes" : "no"]
1714
+ ]);
1715
+ }
1716
+ function displayWalletBalances(balances) {
1717
+ heading("Wallet Funds");
1718
+ table([
1719
+ ["Wallet", balances.walletAddress],
1720
+ ["Chain", `${NETWORK_NAME} (${balances.chainId})`],
1721
+ ["Available pUSD", displayAmount(balances.available)],
1722
+ ["USDC.e", displayAmount(balances.assets.usdcE)],
1723
+ [displayAssetSymbol(balances.assets.matic), displayAmount(balances.assets.matic)],
1724
+ ["Updated", new Date(balances.updatedAt).toLocaleString()]
1725
+ ]);
1726
+ }
1727
+ function displayDepositInstructions(instructions) {
1728
+ heading("Deposit");
1729
+ table([
1730
+ ["Public deposit address", instructions.publicDepositAddress],
1731
+ ["Network", `${instructions.network} (${instructions.chainId})`]
1732
+ ]);
1733
+ process.stdout.write("\n");
1734
+ table(
1735
+ instructions.assets.map((asset) => [
1736
+ displayAssetSymbol(asset),
1737
+ tokenAddress(asset),
1738
+ displayAmount(asset)
1739
+ ]),
1740
+ ["Asset", "Token contract", "Current balance"]
1741
+ );
1742
+ process.stdout.write("\n");
1743
+ warn(instructions.warning);
1744
+ }
1745
+ function displayBridgeDeposit(result) {
1746
+ const bridgeAddresses = result.bridge?.address ?? {};
1747
+ const rows = Object.entries(bridgeAddresses).filter(([, value]) => typeof value === "string" && value.length > 0).map(([network, value]) => [network.toUpperCase(), value]);
1748
+ if (rows.length === 0) return;
1749
+ process.stdout.write("\n");
1750
+ table(rows, ["Bridge network", "Bridge deposit address"]);
1751
+ if (result.bridge?.note) {
1752
+ process.stdout.write("\n" + dim(` ${result.bridge.note}
1753
+ `));
1754
+ }
1755
+ for (const warning of result.bridge?.warnings ?? []) {
1756
+ if (warning.message) warn(warning.message);
1757
+ }
1758
+ }
1759
+ function displayWithdrawIntent(intent) {
1760
+ heading("Withdraw");
1761
+ table([
1762
+ ["Intent", intent.id],
1763
+ ["Status", intent.status],
1764
+ ["Wallet", intent.walletAddress],
1765
+ ["Amount", `${intent.amount} pUSD`],
1766
+ ["Recipient", intent.recipientAddress],
1767
+ ["Destination", `chain ${intent.toChainId} \xB7 ${intent.toTokenAddress}`],
1768
+ ["Bridge address", intent.bridgeAddresses.evm ?? "(none)"],
1769
+ ["Signing URL", intent.signingUrl]
1770
+ ]);
1771
+ const quote = intent.quote ?? {};
1772
+ if (quote.estOutputUsd !== void 0 || quote.estCheckoutTimeMs !== void 0) {
1773
+ process.stdout.write("\n");
1774
+ table([
1775
+ ["Estimated output", quote.estOutputUsd !== void 0 ? currency(Number(quote.estOutputUsd)) : "-"],
1776
+ ["Estimated checkout", quote.estCheckoutTimeMs !== void 0 ? `${Math.round(Number(quote.estCheckoutTimeMs) / 1e3)}s` : "-"]
1777
+ ]);
1778
+ }
1779
+ }
1780
+ function openBrowser(url) {
1781
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
1782
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
1783
+ const child = spawn(command, args, { detached: true, stdio: "ignore" });
1784
+ child.unref();
1785
+ }
1786
+ function sleep(ms) {
1787
+ return new Promise((resolve) => setTimeout(resolve, ms));
1788
+ }
1789
+ async function pollWithdrawalIntent(client, id, opts) {
1790
+ const started = Date.now();
1791
+ let lastStatus = "";
1792
+ while (Date.now() - started <= opts.timeoutSeconds * 1e3) {
1793
+ const result = await client.get(
1794
+ `/api/wallet/withdraw/intents/${encodeURIComponent(id)}`
1795
+ );
1796
+ const intent = result.intent;
1797
+ if (!opts.jsonMode && intent.status !== lastStatus) {
1798
+ process.stderr.write(dim(` Withdrawal status: ${intent.status}
1799
+ `));
1800
+ lastStatus = intent.status;
1801
+ }
1802
+ if (TERMINAL_WITHDRAWAL_STATUSES.has(intent.status)) return intent;
1803
+ await sleep(opts.intervalSeconds * 1e3);
1804
+ }
1805
+ throw new Error(`Timed out waiting for withdrawal intent ${id}`);
1806
+ }
1807
+ function registerWalletCommands(program2) {
1808
+ const wallet = program2.command("wallet").description("Inspect linked wallet funds and funding instructions");
1809
+ wallet.command("status").description("Show linked owner and Polymarket deposit wallet status").action(async () => {
1810
+ const globalOpts = program2.opts();
1811
+ const client = new ApiClient(globalOpts);
1812
+ requireAuth7(client);
1813
+ const status = await client.get("/api/wallet/status");
1814
+ if (globalOpts.json) {
1815
+ json(status);
1816
+ return;
1817
+ }
1818
+ displayWalletStatus(status);
1819
+ });
1820
+ wallet.command("balances").alias("funds").description("Show available wallet funds").action(async () => {
1821
+ const globalOpts = program2.opts();
1822
+ const client = new ApiClient(globalOpts);
1823
+ requireAuth7(client);
1824
+ const balances = await client.get("/api/wallet/balances");
1825
+ if (globalOpts.json) {
1826
+ json(balances);
1827
+ return;
1828
+ }
1829
+ displayWalletBalances(balances);
1830
+ });
1831
+ wallet.command("deposit").description("Show deposit address and supported Polygon assets").option("--asset <asset>", "pUSD | USDC.e | POL").option("--bridge", "Also request Polymarket Bridge deposit addresses").action(async (opts) => {
1832
+ const globalOpts = program2.opts();
1833
+ const client = new ApiClient(globalOpts);
1834
+ requireAuth7(client);
1835
+ const asset = normalizeWalletAsset(opts.asset);
1836
+ const result = await client.post("/api/wallet/deposit", {
1837
+ bridge: Boolean(opts.bridge)
1838
+ });
1839
+ const balances = {
1840
+ walletAddress: result.direct.walletAddress,
1841
+ chainId: result.direct.chainId,
1842
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1843
+ available: result.direct.assets.pUsd,
1844
+ assets: result.direct.assets
1845
+ };
1846
+ const instructions = buildDepositInstructions(balances, asset);
1847
+ if (globalOpts.json) {
1848
+ json({ ...result, instructions });
1849
+ return;
1850
+ }
1851
+ displayDepositInstructions(instructions);
1852
+ displayBridgeDeposit(result);
1853
+ });
1854
+ wallet.command("withdraw").description("Create a browser-signed withdrawal intent and poll for completion").requiredOption("--to <address>", "Recipient EVM wallet address", parseWalletAddress).requiredOption("--amount <amount>", "Amount to withdraw", parsePositiveNumber2).option("--asset <asset>", "Source asset; currently only pUSD is supported", "pUSD").option("--to-chain-id <id>", "Destination chain id", "137").option("--to-token-address <address>", "Destination token address", parseWalletAddress, POLYGON_NATIVE_USDC).option("--no-open", "Do not open the browser signing URL").option("--no-wait", "Do not poll for completion after creating the intent").option("--poll-interval <seconds>", "Polling interval in seconds", parsePositiveNumber2, 5).option("--timeout <seconds>", "Maximum seconds to wait for completion", parsePositiveNumber2, 600).action(async (opts) => {
1855
+ const globalOpts = program2.opts();
1856
+ const client = new ApiClient(globalOpts);
1857
+ requireAuth7(client);
1858
+ const created = await client.post(
1859
+ "/api/wallet/withdraw/intents",
1860
+ buildWithdrawRequestPayload(opts)
1861
+ );
1862
+ let intent = created.intent;
1863
+ if (opts.open !== false) {
1864
+ openBrowser(intent.signingUrl);
1865
+ }
1866
+ if (opts.wait !== false) {
1867
+ intent = await pollWithdrawalIntent(client, intent.id, {
1868
+ timeoutSeconds: opts.timeout ?? 600,
1869
+ intervalSeconds: opts.pollInterval ?? 5,
1870
+ jsonMode: Boolean(globalOpts.json)
1871
+ });
1872
+ }
1873
+ if (globalOpts.json) {
1874
+ json(intent);
1875
+ return;
1876
+ } else {
1877
+ displayWithdrawIntent(intent);
1878
+ }
1879
+ });
1880
+ }
1881
+
1316
1882
  // src/index.ts
1317
- var program = new Command2();
1318
- program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("1.5.0").option("--json", "Output as JSON (machine-readable)").option("--api-url <url>", "Override API base URL").option("--token <token>", "Override stored API token").option("--verbose", "Show HTTP request details").option("--no-color", "Disable colored output");
1883
+ var program = new Command4();
1884
+ program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("1.7.0").option("--json", "Output as JSON (machine-readable)").option("--api-url <url>", "Override API base URL").option("--token <token>", "Override stored API token").option("--verbose", "Show HTTP request details").option("--no-color", "Disable colored output");
1319
1885
  registerAuthCommands(program);
1320
1886
  registerBriefCommands(program);
1321
1887
  registerProfileCommand(program);
1322
1888
  registerResearchCommands(program);
1323
1889
  registerFeedCommand(program);
1890
+ registerWalletCommands(program);
1891
+ registerLpCommands(program);
1324
1892
  registerAllocatorCommands(program);
1325
1893
  program.hook("preAction", (_thisCommand, actionCommand) => {
1326
1894
  const opts = program.opts();