mpp32-mcp-server 1.3.1 → 1.4.1
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/CHANGELOG.md +50 -0
- package/README.md +22 -0
- package/dist/index.js +87 -1
- package/dist/pivx-provider.d.ts +42 -0
- package/dist/pivx-provider.js +232 -0
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,56 @@ All notable changes to `mpp32-mcp-server` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.4.0] - 2026-05-21
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* **`get_pivx_dao_intelligence` MCP tool — real-time PIVX DAO governance
|
|
12
|
+
intelligence.** Returns active budget proposals with masternode voting
|
|
13
|
+
tallies (Yes/No counts, net yes percentage), budget allocation status,
|
|
14
|
+
network health metrics, and deflation/fee burn analysis. PIVX is a fully
|
|
15
|
+
community-governed cryptocurrency where Masternode owners vote on budget
|
|
16
|
+
proposals every ~30 days (43,200 blocks per superblock cycle, 432,000 PIV
|
|
17
|
+
max monthly budget). Data is sourced live from pivx.org/proposals (HTML
|
|
18
|
+
scraping with structured `data-*` attributes) and the Chainz CryptoID
|
|
19
|
+
blockchain API. Cached for 5 minutes. Free — no payment key required.
|
|
20
|
+
|
|
21
|
+
Parameters:
|
|
22
|
+
- `filter` (optional, enum: `all` | `passing` | `failing`) — filter
|
|
23
|
+
proposals by voting status. Default: `all`.
|
|
24
|
+
- `includeStats` (optional, boolean) — include network stats and deflation
|
|
25
|
+
metrics. Default: `true`.
|
|
26
|
+
|
|
27
|
+
The response includes:
|
|
28
|
+
- **Network Overview:** masternode count, passing threshold (10% of
|
|
29
|
+
masternodes), monthly budget (PIV and USD), budget allocation percentage,
|
|
30
|
+
block height, total/circulating supply.
|
|
31
|
+
- **Deflation Metrics:** unallocated treasury PIV per cycle (never minted),
|
|
32
|
+
annual unallocated estimate, effective inflation reduction percentage,
|
|
33
|
+
proposal submission fee burn (50 PIV per proposal).
|
|
34
|
+
- **Active Proposals:** name, status (passing/failing), funded flag,
|
|
35
|
+
vote counts (yes/no), net yes percentage, monthly and total payment
|
|
36
|
+
amounts (PIV and USD), installments remaining, budget usage percentage,
|
|
37
|
+
and link to proposal details.
|
|
38
|
+
|
|
39
|
+
* **PIVX Governance Oracle catalog entry.** Listed as `curated:pivx-governance`
|
|
40
|
+
in the federated catalog (category: crypto, free, verified). Discoverable
|
|
41
|
+
via `list_mpp32_services` with `q=pivx` or `category=crypto`.
|
|
42
|
+
|
|
43
|
+
* **Backend `/api/governance` endpoints.** Three routes serve the PIVX data:
|
|
44
|
+
`GET /api/governance` (full governance data + deflation), `GET
|
|
45
|
+
/api/governance/proposals?status=passing|failing` (filtered proposals),
|
|
46
|
+
`GET /api/governance/stats` (network stats only). Rate limited to 30
|
|
47
|
+
req/min.
|
|
48
|
+
|
|
49
|
+
* **Frontend `/governance` page** at mpp32.org/governance. Live dashboard
|
|
50
|
+
with proposal cards, vote bars, budget stats, deflation metrics, and an
|
|
51
|
+
explainer on how PIVX governance works.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
* **`server.json` version bumped to 1.4.0** to match the npm release.
|
|
56
|
+
|
|
7
57
|
## [1.3.1] - 2026-05-15
|
|
8
58
|
|
|
9
59
|
### Fixed
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ MPP32 replaces all of that with one MCP server. Your agent asks for a service by
|
|
|
33
33
|
* Browse a live catalog of over 4,500 paid and free APIs across categories like token intelligence, market data, web search, image generation, embeddings, DeFi analytics, wallet scoring, on chain queries, and trading signals.
|
|
34
34
|
* Pay any provider in the catalog using whichever protocol fits, with settlement that lands in seconds.
|
|
35
35
|
* Run real time Solana token intelligence with alpha scoring, rug risk, whale flow, and 24 hour pump probability.
|
|
36
|
+
* Query live PIVX DAO governance data: active proposals, masternode voting tallies, budget allocation, and network deflation metrics — all free, no payment key needed.
|
|
36
37
|
* Track every call, every dollar settled, and every protocol used from a dashboard at mpp32.org.
|
|
37
38
|
* Get an automatic 20 percent or 40 percent discount on native services for holding M32 once your wallet is verified.
|
|
38
39
|
* Access M32-gated exclusive APIs: Whale Tracker (1M M32), Token Comparison (2.5M M32), and Portfolio Scanner (5M M32) — free for holders, unavailable to non-holders.
|
|
@@ -177,6 +178,27 @@ M32-gated full wallet portfolio scan. Discovers all SPL tokens in a wallet, runs
|
|
|
177
178
|
{ "wallet": "<wallet-to-scan>", "walletAddress": "<your-solana-wallet>" }
|
|
178
179
|
```
|
|
179
180
|
|
|
181
|
+
### `get_pivx_dao_intelligence`
|
|
182
|
+
|
|
183
|
+
Real-time PIVX DAO governance intelligence. Returns active budget proposals with masternode voting tallies, budget allocation status, network deflation metrics, and masternode network health. PIVX is a fully community-governed cryptocurrency where Masternode owners vote on budget proposals every ~30 days. Data sourced live from pivx.org/proposals and the PIVX blockchain. Free — no payment key required.
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{ "filter": "all" }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{ "filter": "passing", "includeStats": true }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Two parameters:
|
|
194
|
+
|
|
195
|
+
* **filter** (optional) — `all` (default), `passing`, or `failing`. Filters proposals by voting status.
|
|
196
|
+
* **includeStats** (optional, default true) — include network overview and deflation metrics alongside proposals.
|
|
197
|
+
|
|
198
|
+
The response includes a network overview (masternode count, passing threshold, monthly budget in PIV and USD, budget allocation percentage, block height, supply stats), deflation metrics (unallocated treasury PIV that are never minted, effective inflation reduction, proposal fee burn), and all active proposals with vote counts, payment amounts, installments, and links.
|
|
199
|
+
|
|
200
|
+
View the governance dashboard at [mpp32.org/governance](https://mpp32.org/governance).
|
|
201
|
+
|
|
180
202
|
### `manage_agent_budget`
|
|
181
203
|
|
|
182
204
|
View, set, or reset the economic circuit breaker for your agent session. Prevents runaway spending with infrastructure-level budget enforcement.
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { signX402Payment } from "./x402-signers.js";
|
|
6
|
-
const SERVER_VERSION = "1.
|
|
6
|
+
const SERVER_VERSION = "1.4.1";
|
|
7
7
|
// ── Env loading: trim and sanitize aggressively ─────────────────────────────
|
|
8
8
|
// Copy-paste from Claude Desktop / Cursor / Windsurf JSON config UIs frequently
|
|
9
9
|
// adds trailing \n, \r, NBSP, BOM, or wraps the value in literal quotes. Any
|
|
@@ -574,6 +574,92 @@ server.tool("scan_portfolio_m32", "M32-gated full wallet portfolio scan. Discove
|
|
|
574
574
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }] };
|
|
575
575
|
}
|
|
576
576
|
});
|
|
577
|
+
// ── Tool 7: get_pivx_dao_intelligence ─────────────────────────────────────
|
|
578
|
+
// Self-contained: scrapes pivx.org/proposals + Chainz CryptoID directly.
|
|
579
|
+
// Does NOT depend on any backend endpoint — works for every npm user out of the box.
|
|
580
|
+
import { fetchPivxGovernance } from "./pivx-provider.js";
|
|
581
|
+
server.tool("get_pivx_dao_intelligence", "Get real-time PIVX DAO governance intelligence. Returns active budget proposals with masternode voting tallies (Yes/No counts, net yes percentages), budget allocation status, network deflation metrics (unallocated treasury PIV that are never minted), and masternode network health. PIVX is a fully community-governed cryptocurrency where Masternode owners vote on budget proposals every ~30 days (43,200 blocks per superblock cycle, 432,000 PIV max monthly budget). Data scraped live from pivx.org/proposals and the PIVX blockchain via Chainz CryptoID. Cached for 5 minutes. Free — no payment or API key required.", {
|
|
582
|
+
filter: z
|
|
583
|
+
.enum(["all", "passing", "failing"])
|
|
584
|
+
.default("all")
|
|
585
|
+
.optional()
|
|
586
|
+
.describe("Filter proposals by status: 'all' (default), 'passing' (funded proposals), or 'failing' (below threshold)."),
|
|
587
|
+
includeStats: z
|
|
588
|
+
.boolean()
|
|
589
|
+
.default(true)
|
|
590
|
+
.optional()
|
|
591
|
+
.describe("Include network stats and deflation metrics (default: true)."),
|
|
592
|
+
}, async ({ filter, includeStats }) => {
|
|
593
|
+
try {
|
|
594
|
+
const gov = await fetchPivxGovernance();
|
|
595
|
+
const lines = [];
|
|
596
|
+
lines.push("# PIVX DAO Governance Intelligence");
|
|
597
|
+
lines.push("");
|
|
598
|
+
if (includeStats !== false) {
|
|
599
|
+
const n = gov.network;
|
|
600
|
+
lines.push("## Network Overview");
|
|
601
|
+
lines.push(`- **Masternodes Online:** ${n.masternodeCount.toLocaleString()}`);
|
|
602
|
+
lines.push(`- **Passing Threshold:** ${n.passingThreshold} votes (10% of masternodes)`);
|
|
603
|
+
lines.push(`- **Monthly Budget:** ${n.monthlyBudgetPiv.toLocaleString()} PIV (~$${n.monthlyBudgetUsd.toLocaleString()})`);
|
|
604
|
+
lines.push(`- **Budget Allocated:** ${n.budgetAllocatedPiv.toLocaleString()} PIV (${n.budgetAllocatedPercent}%)`);
|
|
605
|
+
if (n.blockHeight)
|
|
606
|
+
lines.push(`- **Block Height:** ${n.blockHeight.toLocaleString()}`);
|
|
607
|
+
if (n.totalSupply)
|
|
608
|
+
lines.push(`- **Total Supply:** ${Math.round(n.totalSupply).toLocaleString()} PIV`);
|
|
609
|
+
lines.push("");
|
|
610
|
+
const d = gov.deflation;
|
|
611
|
+
lines.push("## Deflation / Fee Burn Metrics");
|
|
612
|
+
lines.push(`- **Unallocated PIV This Cycle:** ${d.unallocatedPivPerCycle.toLocaleString()} PIV (never minted)`);
|
|
613
|
+
lines.push(`- **Annual Unallocated (est.):** ${d.annualUnallocatedPiv.toLocaleString()} PIV`);
|
|
614
|
+
lines.push(`- **Effective Inflation Reduction:** ${d.effectiveInflationReduction}`);
|
|
615
|
+
lines.push(`- **Proposal Submission Fee:** ${d.proposalFeeBurnPiv} PIV (burned/destroyed)`);
|
|
616
|
+
lines.push("");
|
|
617
|
+
}
|
|
618
|
+
let proposals = gov.proposals;
|
|
619
|
+
if (filter === "passing")
|
|
620
|
+
proposals = proposals.filter((p) => p.status === "passing");
|
|
621
|
+
else if (filter === "failing")
|
|
622
|
+
proposals = proposals.filter((p) => p.status === "failing");
|
|
623
|
+
if (proposals.length > 0) {
|
|
624
|
+
lines.push(`## Active Proposals (${proposals.length})`);
|
|
625
|
+
lines.push("");
|
|
626
|
+
for (const p of proposals) {
|
|
627
|
+
const status = p.status === "passing" ? "PASSING" : "FAILING";
|
|
628
|
+
const fundedTag = p.funded ? " (Funded)" : "";
|
|
629
|
+
lines.push(`### ${p.name} — ${status}${fundedTag}`);
|
|
630
|
+
lines.push(`- **Votes:** ${p.yesVotes} Yes / ${p.noVotes} No (${p.netYesPercent}% net yes)`);
|
|
631
|
+
lines.push(`- **Monthly Payment:** ${p.monthlyPaymentPiv.toLocaleString()} PIV (~$${p.monthlyPaymentUsd.toLocaleString()})`);
|
|
632
|
+
if (p.totalPaymentPiv > p.monthlyPaymentPiv) {
|
|
633
|
+
lines.push(`- **Total Budget:** ${p.totalPaymentPiv.toLocaleString()} PIV`);
|
|
634
|
+
}
|
|
635
|
+
if (p.installmentsRemaining > 0) {
|
|
636
|
+
lines.push(`- **Installments Remaining:** ${p.installmentsRemaining}`);
|
|
637
|
+
}
|
|
638
|
+
if (p.budgetPercent)
|
|
639
|
+
lines.push(`- **Budget Usage:** ${p.budgetPercent}%`);
|
|
640
|
+
if (p.url)
|
|
641
|
+
lines.push(`- **Details:** ${p.url}`);
|
|
642
|
+
lines.push("");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
lines.push("No proposals found matching the filter.");
|
|
647
|
+
}
|
|
648
|
+
lines.push("---");
|
|
649
|
+
lines.push(`Source: ${gov.source} | ${gov.timestamp}${gov.cacheHit ? " (cached)" : ""}`);
|
|
650
|
+
return {
|
|
651
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
return {
|
|
656
|
+
content: [{
|
|
657
|
+
type: "text",
|
|
658
|
+
text: `Failed to fetch PIVX governance data: ${err instanceof Error ? err.message : String(err)}. The tool scrapes pivx.org/proposals directly — the site may be temporarily unreachable.`,
|
|
659
|
+
}],
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
});
|
|
577
663
|
// ── Tool 8: manage_agent_budget ────────────────────────────────────────────
|
|
578
664
|
server.tool("manage_agent_budget", "View, set, or reset the spending circuit breaker for your MPP32 agent session. Use 'get' to check current budget status (remaining budget, hourly velocity, circuit breaker state). Use 'set' to configure spending limits (budget cap in USD, hourly velocity limit, alert threshold percentage). Use 'reset' to manually reset a tripped circuit breaker so the session can resume spending. Circuit breakers trip automatically when budget or velocity limits are exceeded, preventing runaway agent spending.", {
|
|
579
665
|
action: z.enum(["get", "set", "reset"]).describe("Action: 'get' = view budget status, 'set' = update limits, 'reset' = clear tripped circuit breaker"),
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface PivxProposal {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
status: "passing" | "failing";
|
|
5
|
+
funded: boolean;
|
|
6
|
+
netYesPercent: number;
|
|
7
|
+
yesVotes: number;
|
|
8
|
+
noVotes: number;
|
|
9
|
+
monthlyPaymentPiv: number;
|
|
10
|
+
monthlyPaymentUsd: number;
|
|
11
|
+
totalPaymentPiv: number;
|
|
12
|
+
installmentsRemaining: number;
|
|
13
|
+
totalInstallments: number;
|
|
14
|
+
budgetPercent: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PivxNetworkStats {
|
|
17
|
+
masternodeCount: number;
|
|
18
|
+
passingThreshold: number;
|
|
19
|
+
monthlyBudgetPiv: number;
|
|
20
|
+
monthlyBudgetUsd: number;
|
|
21
|
+
budgetAllocatedPiv: number;
|
|
22
|
+
budgetAllocatedUsd: number;
|
|
23
|
+
budgetAllocatedPercent: number;
|
|
24
|
+
blockHeight: number;
|
|
25
|
+
totalSupply: number;
|
|
26
|
+
circulatingSupply: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PivxGovernanceData {
|
|
29
|
+
proposals: PivxProposal[];
|
|
30
|
+
network: PivxNetworkStats;
|
|
31
|
+
deflation: {
|
|
32
|
+
unallocatedPivPerCycle: number;
|
|
33
|
+
unallocatedPercent: number;
|
|
34
|
+
annualUnallocatedPiv: number;
|
|
35
|
+
proposalFeeBurnPiv: number;
|
|
36
|
+
effectiveInflationReduction: string;
|
|
37
|
+
};
|
|
38
|
+
timestamp: string;
|
|
39
|
+
source: string;
|
|
40
|
+
cacheHit: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function fetchPivxGovernance(): Promise<PivxGovernanceData>;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import * as cheerio from "cheerio";
|
|
2
|
+
// 5-minute cache
|
|
3
|
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
4
|
+
let cachedData = null;
|
|
5
|
+
let cacheTimestamp = 0;
|
|
6
|
+
const CHAINZ_BASE = "https://chainz.cryptoid.info/pivx/api.dws";
|
|
7
|
+
async function chainzFetch(query) {
|
|
8
|
+
const res = await fetch(`${CHAINZ_BASE}?q=${query}`, {
|
|
9
|
+
signal: AbortSignal.timeout(8000),
|
|
10
|
+
});
|
|
11
|
+
if (!res.ok)
|
|
12
|
+
throw new Error(`Chainz API ${query}: HTTP ${res.status}`);
|
|
13
|
+
return (await res.text()).trim();
|
|
14
|
+
}
|
|
15
|
+
async function fetchNetworkStats() {
|
|
16
|
+
const [masternodeCount, blockHeight, totalSupply, circulating] = await Promise.allSettled([
|
|
17
|
+
chainzFetch("masternodecount"),
|
|
18
|
+
chainzFetch("getblockcount"),
|
|
19
|
+
chainzFetch("totalcoins"),
|
|
20
|
+
chainzFetch("circulating"),
|
|
21
|
+
]);
|
|
22
|
+
const mn = masternodeCount.status === "fulfilled"
|
|
23
|
+
? parseInt(masternodeCount.value, 10)
|
|
24
|
+
: 0;
|
|
25
|
+
const bh = blockHeight.status === "fulfilled"
|
|
26
|
+
? parseInt(blockHeight.value, 10)
|
|
27
|
+
: 0;
|
|
28
|
+
const ts = totalSupply.status === "fulfilled"
|
|
29
|
+
? parseFloat(totalSupply.value)
|
|
30
|
+
: 0;
|
|
31
|
+
const cs = circulating.status === "fulfilled"
|
|
32
|
+
? parseFloat(circulating.value)
|
|
33
|
+
: 0;
|
|
34
|
+
return {
|
|
35
|
+
masternodeCount: isNaN(mn) ? 0 : mn,
|
|
36
|
+
blockHeight: isNaN(bh) ? 0 : bh,
|
|
37
|
+
totalSupply: isNaN(ts) ? 0 : ts,
|
|
38
|
+
circulatingSupply: isNaN(cs) ? 0 : cs,
|
|
39
|
+
passingThreshold: isNaN(mn) ? 0 : Math.ceil(mn * 0.1),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function parseNumber(text) {
|
|
43
|
+
const cleaned = text.replace(/[^0-9.\-]/g, "");
|
|
44
|
+
const num = parseFloat(cleaned);
|
|
45
|
+
return isNaN(num) ? 0 : num;
|
|
46
|
+
}
|
|
47
|
+
async function scrapePivxProposals() {
|
|
48
|
+
const res = await fetch("https://pivx.org/proposals", {
|
|
49
|
+
signal: AbortSignal.timeout(15000),
|
|
50
|
+
headers: {
|
|
51
|
+
"User-Agent": "MPP32-Governance-Oracle/1.0 (+https://mpp32.org)",
|
|
52
|
+
Accept: "text/html",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
throw new Error(`pivx.org/proposals returned HTTP ${res.status}`);
|
|
57
|
+
const html = await res.text();
|
|
58
|
+
const $ = cheerio.load(html);
|
|
59
|
+
const pageText = $("body").text();
|
|
60
|
+
let monthlyBudgetPiv = 432000;
|
|
61
|
+
let monthlyBudgetUsd = 0;
|
|
62
|
+
let budgetAllocatedPiv = 0;
|
|
63
|
+
let budgetAllocatedUsd = 0;
|
|
64
|
+
let masternodeCount = 0;
|
|
65
|
+
let passingThreshold = 0;
|
|
66
|
+
const budgetMatch = pageText.match(/Monthly\s*Budget[:\s]*([\d,]+)\s*PIV/i);
|
|
67
|
+
if (budgetMatch?.[1])
|
|
68
|
+
monthlyBudgetPiv = parseNumber(budgetMatch[1]);
|
|
69
|
+
const budgetUsdMatch = pageText.match(/Monthly\s*Budget[^$]*US?\$([\d,.]+)/i);
|
|
70
|
+
if (budgetUsdMatch?.[1])
|
|
71
|
+
monthlyBudgetUsd = parseNumber(budgetUsdMatch[1]);
|
|
72
|
+
const allocatedMatch = pageText.match(/Budget\s*Allocated[:\s]*([\d,]+)\s*PIV/i);
|
|
73
|
+
if (allocatedMatch?.[1])
|
|
74
|
+
budgetAllocatedPiv = parseNumber(allocatedMatch[1]);
|
|
75
|
+
const allocatedUsdMatch = pageText.match(/Budget\s*Allocated[^$]*US?\$([\d,.]+)/i);
|
|
76
|
+
if (allocatedUsdMatch?.[1])
|
|
77
|
+
budgetAllocatedUsd = parseNumber(allocatedUsdMatch[1]);
|
|
78
|
+
const mnMatch = pageText.match(/([\d,]+)\s*masternodes?\s*online/i);
|
|
79
|
+
if (mnMatch?.[1])
|
|
80
|
+
masternodeCount = parseNumber(mnMatch[1]);
|
|
81
|
+
const thresholdMatch = pageText.match(/Positive\s*votes\s*required[^:]*:\s*(\d+)/i);
|
|
82
|
+
if (thresholdMatch?.[1])
|
|
83
|
+
passingThreshold = parseInt(thresholdMatch[1], 10);
|
|
84
|
+
const proposals = [];
|
|
85
|
+
const seenHashes = new Set();
|
|
86
|
+
$("table#js_table tbody tr[data-hash]").each((_i, el) => {
|
|
87
|
+
const $row = $(el);
|
|
88
|
+
const hash = $row.attr("data-hash") || "";
|
|
89
|
+
if (!hash || seenHashes.has(hash))
|
|
90
|
+
return;
|
|
91
|
+
seenHashes.add(hash);
|
|
92
|
+
const name = ($row.attr("data-title") || "").trim();
|
|
93
|
+
if (!name)
|
|
94
|
+
return;
|
|
95
|
+
const cells = $row.find("td");
|
|
96
|
+
if (cells.length < 5)
|
|
97
|
+
return;
|
|
98
|
+
const statusCell = $(cells[0]);
|
|
99
|
+
const statusText = statusCell.text();
|
|
100
|
+
const isPassing = /passing/i.test(statusText);
|
|
101
|
+
const funded = /funded/i.test(statusText);
|
|
102
|
+
let netYesPercent = 0;
|
|
103
|
+
const netYesMatch = statusText.match(/([-\d.]+)%/);
|
|
104
|
+
if (netYesMatch?.[1])
|
|
105
|
+
netYesPercent = parseFloat(netYesMatch[1]);
|
|
106
|
+
const nameCell = $(cells[1]);
|
|
107
|
+
const link = nameCell.find("a").first();
|
|
108
|
+
const url = link.attr("href") || "";
|
|
109
|
+
const paymentCell = $(cells[2]);
|
|
110
|
+
const monthlyPaymentPiv = parseNumber(paymentCell.attr("data-piv") || paymentCell.attr("data-order") || "0");
|
|
111
|
+
const budgetPercent = parseFloat(paymentCell.attr("data-percent") || "0");
|
|
112
|
+
let monthlyPaymentUsd = 0;
|
|
113
|
+
const usdSpan = paymentCell.find(".curr-prefix").parent();
|
|
114
|
+
if (usdSpan.length) {
|
|
115
|
+
const usdText = usdSpan.text();
|
|
116
|
+
const usdMatch = usdText.match(/US?\$\s*([\d,]+(?:\.\d+)?)/i);
|
|
117
|
+
if (usdMatch?.[1])
|
|
118
|
+
monthlyPaymentUsd = parseNumber(usdMatch[1]);
|
|
119
|
+
}
|
|
120
|
+
let installmentsRemaining = 1;
|
|
121
|
+
const longLine = paymentCell.find(".long-line");
|
|
122
|
+
if (longLine.length) {
|
|
123
|
+
const installB = longLine.find("b").first();
|
|
124
|
+
if (installB.length) {
|
|
125
|
+
const n = parseInt(installB.text().trim(), 10);
|
|
126
|
+
if (!isNaN(n))
|
|
127
|
+
installmentsRemaining = n;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
let totalPaymentPiv = monthlyPaymentPiv;
|
|
131
|
+
if (longLine.length) {
|
|
132
|
+
const totalB = longLine.find("b").last();
|
|
133
|
+
if (totalB.length) {
|
|
134
|
+
const totalText = totalB.text();
|
|
135
|
+
const totalMatch = totalText.match(/([\d,]+(?:\.\d+)?)\s*PIV/i);
|
|
136
|
+
if (totalMatch?.[1])
|
|
137
|
+
totalPaymentPiv = parseNumber(totalMatch[1]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const voteCell = $(cells[4]);
|
|
141
|
+
const voteText = voteCell.text();
|
|
142
|
+
let yesVotes = 0;
|
|
143
|
+
let noVotes = 0;
|
|
144
|
+
const voteMatch = voteText.match(/(\d+)\s*\/\s*(\d+)/);
|
|
145
|
+
if (voteMatch?.[1] && voteMatch[2]) {
|
|
146
|
+
yesVotes = parseInt(voteMatch[1], 10);
|
|
147
|
+
noVotes = parseInt(voteMatch[2], 10);
|
|
148
|
+
}
|
|
149
|
+
proposals.push({
|
|
150
|
+
name,
|
|
151
|
+
url,
|
|
152
|
+
status: isPassing ? "passing" : "failing",
|
|
153
|
+
funded,
|
|
154
|
+
netYesPercent,
|
|
155
|
+
yesVotes,
|
|
156
|
+
noVotes,
|
|
157
|
+
monthlyPaymentPiv,
|
|
158
|
+
monthlyPaymentUsd,
|
|
159
|
+
totalPaymentPiv,
|
|
160
|
+
installmentsRemaining,
|
|
161
|
+
totalInstallments: installmentsRemaining,
|
|
162
|
+
budgetPercent,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
proposals,
|
|
167
|
+
budgetSummary: {
|
|
168
|
+
monthlyBudgetPiv,
|
|
169
|
+
monthlyBudgetUsd,
|
|
170
|
+
budgetAllocatedPiv,
|
|
171
|
+
budgetAllocatedUsd,
|
|
172
|
+
masternodeCount,
|
|
173
|
+
passingThreshold,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export async function fetchPivxGovernance() {
|
|
178
|
+
if (cachedData && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
|
|
179
|
+
return { ...cachedData, cacheHit: true };
|
|
180
|
+
}
|
|
181
|
+
const [scrapeResult, networkStats] = await Promise.allSettled([
|
|
182
|
+
scrapePivxProposals(),
|
|
183
|
+
fetchNetworkStats(),
|
|
184
|
+
]);
|
|
185
|
+
const scraped = scrapeResult.status === "fulfilled" ? scrapeResult.value : null;
|
|
186
|
+
const chainzStats = networkStats.status === "fulfilled" ? networkStats.value : {};
|
|
187
|
+
if (!scraped) {
|
|
188
|
+
throw new Error(`Failed to fetch PIVX governance data: ${scrapeResult.status === "rejected" ? scrapeResult.reason : "unknown"}`);
|
|
189
|
+
}
|
|
190
|
+
const budgetAllocatedPercent = scraped.budgetSummary.monthlyBudgetPiv > 0
|
|
191
|
+
? Math.round((scraped.budgetSummary.budgetAllocatedPiv /
|
|
192
|
+
scraped.budgetSummary.monthlyBudgetPiv) *
|
|
193
|
+
10000) / 100
|
|
194
|
+
: 0;
|
|
195
|
+
const network = {
|
|
196
|
+
masternodeCount: scraped.budgetSummary.masternodeCount ||
|
|
197
|
+
chainzStats.masternodeCount ||
|
|
198
|
+
0,
|
|
199
|
+
passingThreshold: scraped.budgetSummary.passingThreshold ||
|
|
200
|
+
chainzStats.passingThreshold ||
|
|
201
|
+
0,
|
|
202
|
+
monthlyBudgetPiv: scraped.budgetSummary.monthlyBudgetPiv,
|
|
203
|
+
monthlyBudgetUsd: scraped.budgetSummary.monthlyBudgetUsd,
|
|
204
|
+
budgetAllocatedPiv: scraped.budgetSummary.budgetAllocatedPiv,
|
|
205
|
+
budgetAllocatedUsd: scraped.budgetSummary.budgetAllocatedUsd,
|
|
206
|
+
budgetAllocatedPercent,
|
|
207
|
+
blockHeight: chainzStats.blockHeight || 0,
|
|
208
|
+
totalSupply: chainzStats.totalSupply || 0,
|
|
209
|
+
circulatingSupply: chainzStats.circulatingSupply || 0,
|
|
210
|
+
};
|
|
211
|
+
const unallocatedPivPerCycle = network.monthlyBudgetPiv - network.budgetAllocatedPiv;
|
|
212
|
+
const annualUnallocatedPiv = unallocatedPivPerCycle * 12;
|
|
213
|
+
const data = {
|
|
214
|
+
proposals: scraped.proposals,
|
|
215
|
+
network,
|
|
216
|
+
deflation: {
|
|
217
|
+
unallocatedPivPerCycle,
|
|
218
|
+
unallocatedPercent: 100 - budgetAllocatedPercent,
|
|
219
|
+
annualUnallocatedPiv,
|
|
220
|
+
proposalFeeBurnPiv: 50,
|
|
221
|
+
effectiveInflationReduction: network.totalSupply > 0
|
|
222
|
+
? `${((annualUnallocatedPiv / network.totalSupply) * 100).toFixed(3)}%`
|
|
223
|
+
: "N/A",
|
|
224
|
+
},
|
|
225
|
+
timestamp: new Date().toISOString(),
|
|
226
|
+
source: "pivx.org/proposals + chainz.cryptoid.info",
|
|
227
|
+
cacheHit: false,
|
|
228
|
+
};
|
|
229
|
+
cachedData = data;
|
|
230
|
+
cacheTimestamp = Date.now();
|
|
231
|
+
return data;
|
|
232
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mpp32-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"mcpName": "io.github.MPP32/mpp32-mcp-server",
|
|
5
5
|
"description": "Payment layer for AI agents. One MCP, five protocols, thousands of paid APIs your agent can call.",
|
|
6
6
|
"type": "module",
|
|
@@ -52,7 +52,10 @@
|
|
|
52
52
|
"crypto",
|
|
53
53
|
"defi",
|
|
54
54
|
"token-intelligence",
|
|
55
|
-
"api-marketplace"
|
|
55
|
+
"api-marketplace",
|
|
56
|
+
"pivx",
|
|
57
|
+
"governance",
|
|
58
|
+
"dao"
|
|
56
59
|
],
|
|
57
60
|
"license": "MIT",
|
|
58
61
|
"author": "MPP32 <admin@mpp32.org> (https://mpp32.org)",
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
"@solana-program/token": "^0.13.0",
|
|
74
77
|
"@solana/kit": "^6.9.0",
|
|
75
78
|
"bs58": "^6.0.0",
|
|
79
|
+
"cheerio": "^1.2.0",
|
|
76
80
|
"tweetnacl": "^1.0.3",
|
|
77
81
|
"viem": "^2.48.11",
|
|
78
82
|
"zod": "^3.23.0"
|