@parallel-protocol/cli 0.2.7 → 0.2.8

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.
Files changed (2) hide show
  1. package/dist/index.js +87 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1635,7 +1635,7 @@ function suppressLogs() {
1635
1635
 
1636
1636
  // package.json
1637
1637
  var package_default = {
1638
- version: "0.2.7"};
1638
+ version: "0.2.8"};
1639
1639
  function findEnvFile(dir) {
1640
1640
  const candidate = join(dir, ".env");
1641
1641
  if (existsSync(candidate)) return candidate;
@@ -5722,7 +5722,7 @@ function toTxData(tx) {
5722
5722
  function quoteCmd() {
5723
5723
  return new Command("quote").description("Get a LayerZero bridge fee quote").option(
5724
5724
  "--token <token>",
5725
- "token to bridge: usdp|susdp|prl (default: usdp)",
5725
+ "token to bridge: usdp (the only token currently bridgeable)",
5726
5726
  "usdp"
5727
5727
  ).option("--from <chain>", "source chain").option("--to <chain>", "destination chain").option("--amount <n>", "amount to bridge").action(async function() {
5728
5728
  const opts = resolveOpts(this);
@@ -5772,7 +5772,11 @@ function quoteCmd() {
5772
5772
  });
5773
5773
  }
5774
5774
  function feesCmd() {
5775
- return new Command("fees").description("Show LayerZero bridge fees to all destinations from a chain").option("--token <token>", "token: usdp|susdp|prl (default: usdp)", "usdp").option("--from <chain>", "source chain").action(async function() {
5775
+ return new Command("fees").description("Show LayerZero bridge fees to all destinations from a chain").option(
5776
+ "--token <token>",
5777
+ "token: usdp (the only token currently bridgeable)",
5778
+ "usdp"
5779
+ ).option("--from <chain>", "source chain").action(async function() {
5776
5780
  const opts = resolveOpts(this);
5777
5781
  try {
5778
5782
  if (!opts.from)
@@ -5805,7 +5809,7 @@ function feesCmd() {
5805
5809
  function sendCmd() {
5806
5810
  return new Command("send").description("Bridge tokens cross-chain via LayerZero OFT").option(
5807
5811
  "--token <token>",
5808
- "token to bridge: usdp|susdp|prl (default: usdp)",
5812
+ "token to bridge: usdp (the only token currently bridgeable)",
5809
5813
  "usdp"
5810
5814
  ).option("--from <chain>", "source chain").option("--to <chain>", "destination chain").option("--amount <n>", "amount to bridge").option("--recipient <addr>", "override destination recipient address").option("-w, --wallet [path|env]", "keystore path or 'env'").option("--address <addr>", "sender address (for dry-run without --wallet)").option("--dry-run", "build tx without broadcasting").action(async function() {
5811
5815
  const opts = resolveOpts(this);
@@ -7153,7 +7157,11 @@ var TTL = {
7153
7157
  SUPPLY: 6e4,
7154
7158
  SAVINGS_RATE: 3e4,
7155
7159
  EXCHANGE_RATE: 3e4,
7156
- STAKING: 6e4};
7160
+ STAKING: 6e4,
7161
+ // Forum topics do not move once created — a long TTL also makes the
7162
+ // Discourse search deterministic across consecutive calls.
7163
+ FORUM_URL: 6e5
7164
+ };
7157
7165
  var lru = new I({
7158
7166
  max: 500,
7159
7167
  allowStale: false
@@ -7689,29 +7697,37 @@ async function getFeeRates(chain, collateral) {
7689
7697
  );
7690
7698
  }
7691
7699
 
7700
+ // ../shared/src/infra/http.ts
7701
+ function fetchWithTimeout(url, init = {}, timeoutMs = 1e4) {
7702
+ return fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
7703
+ }
7704
+
7692
7705
  // ../mcp-server/src/services/governance/proposals.ts
7693
7706
  var SNAPSHOT_HUB = "https://hub.snapshot.org/graphql";
7694
7707
  var SNAPSHOT_SPACE = "parallel-protocol.eth";
7695
7708
  var DISCOURSE_BASE = "https://gov.parallel.best";
7696
- async function fetchForumUrl(friendlyId) {
7697
- try {
7698
- const res = await fetch(
7699
- `${DISCOURSE_BASE}/search.json?q=${encodeURIComponent(friendlyId)}`
7700
- );
7701
- if (!res.ok) return null;
7702
- const data = await res.json();
7703
- const topic = data.topics?.[0];
7704
- if (!topic) return null;
7705
- return `${DISCOURSE_BASE}/t/${topic.slug}/${topic.id}`;
7706
- } catch {
7707
- return null;
7708
- }
7709
+ function fetchForumUrl(friendlyId) {
7710
+ return getOrSet(`gov:forum:${friendlyId}`, TTL.FORUM_URL, async () => {
7711
+ try {
7712
+ const res = await fetchWithTimeout(
7713
+ `${DISCOURSE_BASE}/search.json?q=${encodeURIComponent(friendlyId)}`,
7714
+ {},
7715
+ 3e3
7716
+ );
7717
+ if (!res.ok) return null;
7718
+ const data = await res.json();
7719
+ const topic = data.topics?.[0];
7720
+ if (!topic) return null;
7721
+ return `${DISCOURSE_BASE}/t/${topic.slug}/${topic.id}`;
7722
+ } catch {
7723
+ return null;
7724
+ }
7725
+ });
7709
7726
  }
7710
7727
  async function enrichWithForumUrls(proposals) {
7711
7728
  const urls = await Promise.all(
7712
7729
  proposals.map((p) => {
7713
- if (p.forumUrl?.startsWith(DISCOURSE_BASE))
7714
- return Promise.resolve(p.forumUrl);
7730
+ if (p.forumUrl) return Promise.resolve(p.forumUrl);
7715
7731
  if (p.id !== p.snapshotId) return fetchForumUrl(p.id);
7716
7732
  return Promise.resolve(null);
7717
7733
  })
@@ -7719,11 +7735,18 @@ async function enrichWithForumUrls(proposals) {
7719
7735
  return proposals.map((p, i) => ({ ...p, forumUrl: urls[i] ?? null }));
7720
7736
  }
7721
7737
  async function querySnapshot(query, variables) {
7722
- const res = await fetch(SNAPSHOT_HUB, {
7723
- method: "POST",
7724
- headers: { "Content-Type": "application/json", Accept: "application/json" },
7725
- body: JSON.stringify({ query, variables })
7726
- });
7738
+ const res = await fetchWithTimeout(
7739
+ SNAPSHOT_HUB,
7740
+ {
7741
+ method: "POST",
7742
+ headers: {
7743
+ "Content-Type": "application/json",
7744
+ Accept: "application/json"
7745
+ },
7746
+ body: JSON.stringify({ query, variables })
7747
+ },
7748
+ 1e4
7749
+ );
7727
7750
  if (!res.ok)
7728
7751
  throw new Error(`Snapshot API error: ${res.status} ${res.statusText}`);
7729
7752
  const json = await res.json();
@@ -7743,14 +7766,23 @@ function inferFriendlyId(title) {
7743
7766
  const match = title.match(/\b(PIP|PGP|PIR)-(\d+(?:\.\d+)?)/i);
7744
7767
  return match ? `${match[1].toUpperCase()}-${match[2]}` : null;
7745
7768
  }
7746
- function mapDetailedStatus(state, scoresTotal, quorum) {
7747
- if (state === "active") return "active";
7748
- if (state === "pending") return "pending";
7749
- if (state === "closed") {
7750
- if (scoresTotal < quorum) return "rejected";
7769
+ function isForAgainstVote(choices) {
7770
+ if (!choices || choices.length < 2) return false;
7771
+ const second = choices[1].toLowerCase();
7772
+ return second.startsWith("against") || second.startsWith("no");
7773
+ }
7774
+ function mapDetailedStatus(p) {
7775
+ if (p.state === "active") return "active";
7776
+ if (p.state === "pending") return "pending";
7777
+ if (p.state === "closed") {
7778
+ if (p.quorum > 0 && p.scores_total < p.quorum) return "rejected";
7779
+ if (isForAgainstVote(p.choices)) {
7780
+ const scores = p.scores ?? [];
7781
+ return (scores[0] ?? 0) > (scores[1] ?? 0) ? "passed" : "rejected";
7782
+ }
7751
7783
  return "passed";
7752
7784
  }
7753
- return state;
7785
+ return p.state;
7754
7786
  }
7755
7787
  function summarizeProposal(p) {
7756
7788
  const scores = p.scores ?? [];
@@ -7763,11 +7795,11 @@ function summarizeProposal(p) {
7763
7795
  title: p.title,
7764
7796
  author: p.author,
7765
7797
  type: inferProposalType(p.title),
7766
- status: mapDetailedStatus(p.state, p.scores_total, p.quorum),
7798
+ status: mapDetailedStatus(p),
7767
7799
  createdAt: new Date(p.created * 1e3).toISOString(),
7768
7800
  votingEndsAt: new Date(p.end * 1e3).toISOString(),
7769
7801
  snapshotUrl: `https://snapshot.org/#/${SNAPSHOT_SPACE}/proposal/${p.id}`,
7770
- forumUrl: p.link ?? null,
7802
+ forumUrl: p.discussion?.trim() ? p.discussion : null,
7771
7803
  summary: (p.body ?? "").slice(0, 200) + ((p.body?.length ?? 0) > 200 ? "\u2026" : ""),
7772
7804
  forVotes,
7773
7805
  againstVotes,
@@ -7795,7 +7827,7 @@ var PROPOSALS_QUERY = `
7795
7827
  scores
7796
7828
  choices
7797
7829
  quorum
7798
- link
7830
+ discussion
7799
7831
  }
7800
7832
  }
7801
7833
  `;
@@ -7838,7 +7870,7 @@ var PROPOSAL_DETAILS_QUERY = `
7838
7870
  scores
7839
7871
  choices
7840
7872
  quorum
7841
- link
7873
+ discussion
7842
7874
  }
7843
7875
  }
7844
7876
  `;
@@ -7846,23 +7878,28 @@ async function buildProposalDetails(p) {
7846
7878
  const [enriched] = await enrichWithForumUrls([summarizeProposal(p)]);
7847
7879
  const summary = enriched;
7848
7880
  const scores = p.scores ?? [];
7881
+ const hasQuorum = p.quorum > 0;
7882
+ const quorumReached = hasQuorum ? p.scores_total >= p.quorum : null;
7849
7883
  return {
7850
7884
  ...summary,
7851
7885
  description: p.body ?? "",
7852
7886
  choices: p.choices ?? [],
7853
7887
  scores,
7854
7888
  quorum: p.quorum,
7855
- quorumReached: p.scores_total >= p.quorum,
7889
+ quorumReached,
7856
7890
  voteResults: {
7857
7891
  for: scores[0] ?? 0,
7858
7892
  against: scores[1] ?? 0,
7859
7893
  abstain: scores[2] ?? 0,
7860
7894
  quorum: p.quorum,
7861
- quorumReached: p.scores_total >= p.quorum
7895
+ quorumReached
7862
7896
  },
7863
7897
  parametersChanged: null,
7864
7898
  executionTx: null,
7865
7899
  unavailableFields: {
7900
+ ...hasQuorum ? {} : {
7901
+ quorumReached: "No quorum is configured for this space on Snapshot (proposal quorum is 0) \u2014 pass/fail is derived from the For/Against split alone."
7902
+ },
7866
7903
  parametersChanged: "Snapshot does not expose structured on-chain parameter changes \u2014 this information is in the proposal body text.",
7867
7904
  executionTx: "Snapshot does not record on-chain execution transactions \u2014 check the DAO's AccessManager or TimelockController on Etherscan."
7868
7905
  }
@@ -8161,7 +8198,7 @@ function proposalsCmd() {
8161
8198
  `--type must be PIP, PGP or PIR, got "${opts.type}"`
8162
8199
  );
8163
8200
  }
8164
- const STATUSES = ["active", "passed", "rejected", "executed"];
8201
+ const STATUSES = ["active", "pending", "passed", "rejected"];
8165
8202
  if (opts.status && !STATUSES.includes(opts.status)) {
8166
8203
  throw new CliError(
8167
8204
  ErrorCode.INVALID_OPTION,
@@ -8208,7 +8245,11 @@ function proposalCmd() {
8208
8245
  ["Created", fmtDate(result.createdAt)],
8209
8246
  ["Voting Ends", fmtDate(result.votingEndsAt)],
8210
8247
  ["Quorum", fmtVotes(result.quorum)],
8211
- ["Quorum Reached", result.quorumReached ? "Yes \u2713" : "No \u2717"],
8248
+ [
8249
+ "Quorum Reached",
8250
+ // null = no quorum configured — "No ✗" would be misleading.
8251
+ result.quorumReached === null ? "\u2014" : result.quorumReached ? "Yes \u2713" : "No \u2717"
8252
+ ],
8212
8253
  ["Snapshot URL", result.snapshotUrl],
8213
8254
  ["Forum URL", result.forumUrl ?? "\u2014"]
8214
8255
  ]);
@@ -8383,9 +8424,9 @@ async function getPaymentCapabilities(chain, {
8383
8424
  const hasUSDp = !!getUSDpAddress(chain);
8384
8425
  const hasSUSDp = !!CHAINS[chain].contracts.susdp;
8385
8426
  const hasUSDC = !!getUSDCAddress(chain);
8386
- const sunset = CHAINS[chain].status === "sunset";
8387
- const parallelizerActive = hasParallelizer && !sunset;
8388
- const susdpVaultActive = hasSUSDp && !sunset;
8427
+ const chainActive = CHAINS[chain].status === "active";
8428
+ const parallelizerActive = hasParallelizer && chainActive;
8429
+ const susdpVaultActive = hasSUSDp && chainActive;
8389
8430
  const facilitatorSupported = parallelizerActive;
8390
8431
  const gasSponsored = parallelizerActive && chain !== "ethereum";
8391
8432
  let hasUsdcCollateral = false;
@@ -8409,7 +8450,7 @@ async function getPaymentCapabilities(chain, {
8409
8450
  }
8410
8451
  }
8411
8452
  const capabilities = [];
8412
- if (hasUSDp) {
8453
+ if (hasUSDp && facilitatorSupported) {
8413
8454
  capabilities.push({
8414
8455
  route: "A",
8415
8456
  method: "transferWithAuthorization",
@@ -8449,7 +8490,7 @@ async function getPaymentCapabilities(chain, {
8449
8490
  gasSponsored
8450
8491
  });
8451
8492
  }
8452
- if (hasUSDp && susdpVaultActive) {
8493
+ if (hasUSDp && susdpVaultActive && facilitatorSupported) {
8453
8494
  capabilities.push({
8454
8495
  route: "C",
8455
8496
  method: "depositWithAuthorization",
@@ -8469,7 +8510,7 @@ async function getPaymentCapabilities(chain, {
8469
8510
  gasSponsored
8470
8511
  });
8471
8512
  }
8472
- if (hasUSDC) {
8513
+ if (hasUSDC && facilitatorSupported) {
8473
8514
  capabilities.push({
8474
8515
  route: "F",
8475
8516
  method: "transferWithAuthorization",
@@ -15319,7 +15360,7 @@ function swapCommand() {
15319
15360
 
15320
15361
  // ../mcp-server/package.json
15321
15362
  var package_default2 = {
15322
- version: "1.0.0"};
15363
+ version: "1.1.0"};
15323
15364
 
15324
15365
  // src/commands/version.ts
15325
15366
  function versionCommand() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parallel-protocol/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [