@continuumdao/ctm-mpc-defi 0.2.47 → 0.2.48

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.
@@ -42,6 +42,7 @@ Do **not** answer from `ctm_continuum_dao_fetch_proposals` or backend `status` a
42
42
  - **`recentlyCompleted`** — Canceled / Defeated / Expired / Executed in this scan.
43
43
  - **`unknown`** — no on-chain overlay (missing governor/RPC, or a failed `state` read). Do not call these live.
44
44
  4. If `overlay` is `backend-only`, say you could not confirm live Governor state.
45
+ 5. After naming a proposal, offer `ctm_continuum_dao_fetch_proposal_votes` so the operator can see who voted and how (For/Against/Abstain, or Delta option weights) and whether each voting address is attached to a node (`NodeProperties.attachedTokenId` after `attachNodeFor`). If they entered NodeInfo at attach, it is on `nodeInfo` — mention forum handle / VPS / DID; do not read email or IP aloud unless asked. Works for live and past rows.
45
46
 
46
47
  For one proposal, call `ctm_continuum_dao_explain_proposal` and read `briefing` aloud (every action / Delta option, no-ops labeled signaling). `network.c3governor` is always present; call C3 out only when `network.name` is not Linea. Catalog ids **-2** and **-1** are the two proposals on the superseded Linea governor `0x4800F9f1dC1b6daCA841B71E0531F547D374168E`. Do not vote from this protocol skill.
47
48
 
@@ -114,6 +115,7 @@ Propose builders **hard-fail** if `getVotes(KeyGen) <` live `proposalThreshold()
114
115
  | Fetch raw backend list | `ctm_continuum_dao_fetch_proposals` |
115
116
  | Fetch one proposal | `ctm_continuum_dao_fetch_proposal` |
116
117
  | Fetch on-chain state | `ctm_continuum_dao_fetch_proposal_state` |
118
+ | Who voted and how | `ctm_continuum_dao_fetch_proposal_votes` |
117
119
  | Fetch voting power | `ctm_continuum_dao_fetch_voting_power` |
118
120
  | Forum sign-in eligible? | `ctm_continuum_dao_forum_sign_in_eligible` |
119
121
  | Forum KeyGen sign-in | `ctm_continuum_dao_build_forum_sign_in_multisign` |
@@ -2306,7 +2306,7 @@ function summarizeLiveProposals(result) {
2306
2306
  const label = (p) => `#${p.id ?? "\u2014"} ${p.title}${p.committeeProposer ? " [Committee]" : ""}`;
2307
2307
  const liveTitles = result.live.map(label).join("; ") || "none";
2308
2308
  const execTitles = result.readyToExecute.map(label).join("; ") || "none";
2309
- return `Live (Active, voting now): ${result.live.length} (${liveTitles}). Pending: ${result.pending.length}. Passed, ready to execute (Succeeded): ${result.readyToExecute.length} (${execTitles}). Recently completed in this scan: ${result.recentlyCompleted.length}. Queue is not implemented.`;
2309
+ return `Live (Active, voting now): ${result.live.length} (${liveTitles}). Pending: ${result.pending.length}. Passed, ready to execute (Succeeded): ${result.readyToExecute.length} (${execTitles}). Recently completed in this scan: ${result.recentlyCompleted.length}. Queue is not implemented. After naming a proposal, offer ctm_continuum_dao_fetch_proposal_votes so the operator can see who voted and how.`;
2310
2310
  }
2311
2311
  async function mapPool(items, concurrency, fn) {
2312
2312
  const out = [];
@@ -4253,6 +4253,465 @@ async function continuumDaoComposeProposalAction(args) {
4253
4253
 
4254
4254
  // src/protocols/evm/continuum-dao/index.ts
4255
4255
  init_explainProposal();
4256
+
4257
+ // src/protocols/evm/continuum-dao/proposalVotes.ts
4258
+ init_constants();
4259
+ init_governance();
4260
+ var VOTE_CAST_EVENT = viem.parseAbiItem(
4261
+ "event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason)"
4262
+ );
4263
+ var VOTE_CAST_WITH_PARAMS_EVENT = viem.parseAbiItem(
4264
+ "event VoteCastWithParams(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason, bytes params)"
4265
+ );
4266
+ var snapshotDeadlineAbi = viem.parseAbi([
4267
+ "function proposalSnapshot(uint256 proposalId) view returns (uint256)",
4268
+ "function proposalDeadline(uint256 proposalId) view returns (uint256)",
4269
+ "function token() view returns (address)"
4270
+ ]);
4271
+ var veNodePropertiesAbi = viem.parseAbi(["function nodeProperties() view returns (address)"]);
4272
+ var nodePropsAttachAbi = viem.parseAbi([
4273
+ "function attachedTokenId(address keyGen) view returns (uint256)"
4274
+ ]);
4275
+ var nodeInfoAbi = [
4276
+ {
4277
+ type: "function",
4278
+ name: "nodeInfo",
4279
+ stateMutability: "view",
4280
+ inputs: [
4281
+ { name: "_tokenId", type: "uint256" },
4282
+ { name: "_account", type: "address" }
4283
+ ],
4284
+ outputs: [
4285
+ {
4286
+ name: "",
4287
+ type: "tuple",
4288
+ components: [
4289
+ { name: "forumHandle", type: "string" },
4290
+ { name: "email", type: "string" },
4291
+ { name: "ipv4", type: "uint8[4]" },
4292
+ { name: "ipv6", type: "uint16[8]" },
4293
+ { name: "vpsProvider", type: "string" },
4294
+ { name: "ramInstalled", type: "uint256" },
4295
+ { name: "cpuCores", type: "uint256" },
4296
+ { name: "dIDType", type: "string" },
4297
+ { name: "dID", type: "string" },
4298
+ { name: "data", type: "bytes" }
4299
+ ]
4300
+ }
4301
+ ]
4302
+ }
4303
+ ];
4304
+ var BRAVO_SUPPORT_LABELS = ["Against", "For", "Abstain"];
4305
+ var LOG_CHUNK_BLOCKS = 8000n;
4306
+ var MAX_LOG_CHUNKS = 80;
4307
+ var MAX_VOTES = 2e3;
4308
+ var TIMESTAMP_FLOOR = 1600000000n;
4309
+ var LINEA_BLOCK_SECONDS = 2n;
4310
+ var RANGE_BUFFER_BLOCKS = 20000n;
4311
+ function bravoSupportLabel(support) {
4312
+ return BRAVO_SUPPORT_LABELS[support] ?? `support=${support}`;
4313
+ }
4314
+ function decodeDeltaVoteParams(params) {
4315
+ if (!params || params === "0x") return [];
4316
+ const hex = params.startsWith("0x") ? params.slice(2) : params;
4317
+ if (hex.length === 0 || hex.length % 64 !== 0) return [];
4318
+ const out = [];
4319
+ for (let i = 0; i < hex.length; i += 64) {
4320
+ out.push(BigInt(`0x${hex.slice(i, i + 64)}`).toString());
4321
+ }
4322
+ return out;
4323
+ }
4324
+ function looksLikeUnixSeconds(value) {
4325
+ return value >= TIMESTAMP_FLOOR && value < 4000000000n;
4326
+ }
4327
+ function estimateBlockAtTimestamp(timestamp, latest) {
4328
+ if (timestamp >= latest.timestamp) return latest.number;
4329
+ const deltaSec = latest.timestamp - timestamp;
4330
+ const approx = deltaSec / LINEA_BLOCK_SECONDS + RANGE_BUFFER_BLOCKS;
4331
+ return latest.number > approx ? latest.number - approx : 0n;
4332
+ }
4333
+ function explorerBase(chainId) {
4334
+ return chainId === exports.CONTINUUM_DAO_LINEA_SEPOLIA_CHAIN_ID ? "https://sepolia.lineascan.build" : "https://lineascan.build";
4335
+ }
4336
+ function howVoted(input) {
4337
+ if (input.optionWeights && input.optionWeights.length > 0) {
4338
+ const labels = input.optionLabels ?? [];
4339
+ const parts = input.optionWeights.map((w, i) => {
4340
+ const last = i === input.optionWeights.length - 1;
4341
+ const name = last ? "NOTA" : labels[i] ?? `option ${i + 1}`;
4342
+ return `${name} ${w}`;
4343
+ });
4344
+ return parts.join(", ");
4345
+ }
4346
+ return bravoSupportLabel(input.support);
4347
+ }
4348
+ function asBigInt(value) {
4349
+ if (typeof value === "bigint") return value;
4350
+ if (typeof value === "number" && Number.isFinite(value)) return BigInt(value);
4351
+ if (typeof value === "string" && /^-?\d+$/.test(value.trim())) return BigInt(value.trim());
4352
+ return null;
4353
+ }
4354
+ function collectVotesFromLogs(logs, proposalId, chainId, optionLabels) {
4355
+ const base = explorerBase(chainId);
4356
+ const rows = [];
4357
+ for (const log of logs) {
4358
+ const args = log.args ?? {};
4359
+ const logProposal = asBigInt(args.proposalId);
4360
+ if (logProposal == null || logProposal !== proposalId) continue;
4361
+ let voter;
4362
+ try {
4363
+ voter = viem.getAddress(String(args.voter ?? ""));
4364
+ } catch {
4365
+ continue;
4366
+ }
4367
+ const support = Number(args.support ?? 0);
4368
+ const weight = asBigInt(args.weight) ?? 0n;
4369
+ const reason = typeof args.reason === "string" ? args.reason : "";
4370
+ const params = typeof args.params === "string" && args.params.startsWith("0x") ? args.params : void 0;
4371
+ const optionWeights = log.eventName === "VoteCastWithParams" || params ? decodeDeltaVoteParams(params) : void 0;
4372
+ const how = howVoted({
4373
+ support,
4374
+ ...optionWeights && optionWeights.length > 0 ? { optionWeights, optionLabels } : {}
4375
+ });
4376
+ const txHash = log.transactionHash ?? void 0;
4377
+ rows.push({
4378
+ voter,
4379
+ explorerUrl: `${base}/address/${voter}`,
4380
+ support,
4381
+ supportLabel: bravoSupportLabel(support),
4382
+ weight: weight.toString(),
4383
+ weightCtm: viem.formatUnits(weight, 18),
4384
+ reason,
4385
+ attached: false,
4386
+ ...optionWeights && optionWeights.length > 0 ? { optionWeights } : {},
4387
+ how,
4388
+ ...txHash ? { txHash, txExplorerUrl: `${base}/tx/${txHash}` } : {},
4389
+ ...log.blockNumber != null ? { blockNumber: log.blockNumber.toString() } : {}
4390
+ });
4391
+ }
4392
+ return rows;
4393
+ }
4394
+ function summarizeProposalVotes(votes) {
4395
+ let forW = 0n;
4396
+ let againstW = 0n;
4397
+ let abstainW = 0n;
4398
+ for (const vote of votes) {
4399
+ const w = BigInt(vote.weight);
4400
+ if (vote.support === 1) forW += w;
4401
+ else if (vote.support === 0) againstW += w;
4402
+ else if (vote.support === 2) abstainW += w;
4403
+ }
4404
+ const note = votes.length === 0 ? "No VoteCast logs for this proposal in the scanned window. If it just started, wait; if it is older than the window, pass fromBlock/toBlock or load official etherscan get_logs on the governor." : `Found ${votes.length} vote(s). Read voter + how + whether they are attached to a node. Offer this after listing or explaining a proposal.`;
4405
+ const attachedCount = votes.filter((vote) => vote.attached).length;
4406
+ const attachBit = votes.length === 0 ? "" : ` ${attachedCount} of ${votes.length} voting address(es) are attached to a node (NodeProperties.attachedTokenId after attachNodeFor).`;
4407
+ return {
4408
+ voterCount: votes.length,
4409
+ attachedVoterCount: attachedCount,
4410
+ forWeight: forW.toString(),
4411
+ againstWeight: againstW.toString(),
4412
+ abstainWeight: abstainW.toString(),
4413
+ note: `${note}${attachBit}`
4414
+ };
4415
+ }
4416
+ function asNumberList(value) {
4417
+ if (!Array.isArray(value)) return [];
4418
+ return value.map((part) => Number(part));
4419
+ }
4420
+ function formatIpv4(parts) {
4421
+ const n = asNumberList(parts);
4422
+ if (n.length !== 4 || n.every((x) => x === 0)) return void 0;
4423
+ return n.join(".");
4424
+ }
4425
+ function formatIpv6(parts) {
4426
+ const n = asNumberList(parts);
4427
+ if (n.length !== 8 || n.every((x) => x === 0)) return void 0;
4428
+ return n.map((x) => x.toString(16)).join(":");
4429
+ }
4430
+ function normalizeNodeInfo(raw) {
4431
+ if (raw == null) return void 0;
4432
+ const rec = Array.isArray(raw) ? {
4433
+ forumHandle: raw[0],
4434
+ email: raw[1],
4435
+ ipv4: raw[2],
4436
+ ipv6: raw[3],
4437
+ vpsProvider: raw[4],
4438
+ ramInstalled: raw[5],
4439
+ cpuCores: raw[6],
4440
+ dIDType: raw[7],
4441
+ dID: raw[8],
4442
+ data: raw[9]
4443
+ } : typeof raw === "object" ? raw : null;
4444
+ if (!rec) return void 0;
4445
+ const str2 = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
4446
+ const ram = asBigInt(rec.ramInstalled);
4447
+ const cpu = asBigInt(rec.cpuCores);
4448
+ const data = typeof rec.data === "string" && rec.data.startsWith("0x") && rec.data !== "0x" ? rec.data : void 0;
4449
+ const info = {
4450
+ ...str2(rec.forumHandle) ? { forumHandle: str2(rec.forumHandle) } : {},
4451
+ ...str2(rec.email) ? { email: str2(rec.email) } : {},
4452
+ ...formatIpv4(rec.ipv4) ? { ipv4: formatIpv4(rec.ipv4) } : {},
4453
+ ...formatIpv6(rec.ipv6) ? { ipv6: formatIpv6(rec.ipv6) } : {},
4454
+ ...str2(rec.vpsProvider) ? { vpsProvider: str2(rec.vpsProvider) } : {},
4455
+ ...ram != null && ram > 0n ? { ramInstalled: ram.toString() } : {},
4456
+ ...cpu != null && cpu > 0n ? { cpuCores: cpu.toString() } : {},
4457
+ ...str2(rec.dIDType) ? { dIDType: str2(rec.dIDType) } : {},
4458
+ ...str2(rec.dID) ? { dID: str2(rec.dID) } : {},
4459
+ ...data ? { dataHex: data } : {}
4460
+ };
4461
+ return Object.keys(info).length > 0 ? info : void 0;
4462
+ }
4463
+ function applyNodeAttachments(votes, attachments) {
4464
+ return votes.map((vote) => {
4465
+ const hit = attachments.get(vote.voter.toLowerCase());
4466
+ if (!hit) return { ...vote, attached: false };
4467
+ return {
4468
+ ...vote,
4469
+ attached: hit.attached,
4470
+ ...hit.attached && hit.attachedTokenId ? { attachedTokenId: hit.attachedTokenId } : {},
4471
+ ...hit.nodeInfo ? { nodeInfo: hit.nodeInfo } : {}
4472
+ };
4473
+ });
4474
+ }
4475
+ function backendOptionLabels(raw) {
4476
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
4477
+ const options = raw.options;
4478
+ if (!Array.isArray(options)) return void 0;
4479
+ const labels = options.map((opt) => {
4480
+ if (!opt || typeof opt !== "object" || Array.isArray(opt)) return "";
4481
+ return String(opt.label ?? "").trim();
4482
+ }).filter(Boolean);
4483
+ return labels.length > 0 ? labels : void 0;
4484
+ }
4485
+ function backendTitle(raw) {
4486
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
4487
+ const title = raw.title;
4488
+ return typeof title === "string" && title.trim() ? title.trim() : void 0;
4489
+ }
4490
+ function backendConfiguration(raw) {
4491
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
4492
+ const cfg = raw.configuration;
4493
+ if (cfg === 1 || cfg === "1" || cfg === "delta") return "delta";
4494
+ if (cfg === 0 || cfg === "0" || cfg === "bravo") return "bravo";
4495
+ return void 0;
4496
+ }
4497
+ async function readWindow(client, gov, proposalId, latest, deps) {
4498
+ let snapshot;
4499
+ let deadline;
4500
+ try {
4501
+ const read = deps?.readSnapshotDeadline ? await deps.readSnapshotDeadline(gov, proposalId) : {
4502
+ snapshot: await client.readContract({
4503
+ address: gov,
4504
+ abi: snapshotDeadlineAbi,
4505
+ functionName: "proposalSnapshot",
4506
+ args: [proposalId]
4507
+ }),
4508
+ deadline: await client.readContract({
4509
+ address: gov,
4510
+ abi: snapshotDeadlineAbi,
4511
+ functionName: "proposalDeadline",
4512
+ args: [proposalId]
4513
+ })
4514
+ };
4515
+ snapshot = read.snapshot;
4516
+ deadline = read.deadline;
4517
+ } catch {
4518
+ }
4519
+ const toFromTs = (ts, fallback) => {
4520
+ if (ts == null) return fallback;
4521
+ if (looksLikeUnixSeconds(ts)) return estimateBlockAtTimestamp(ts, latest);
4522
+ return ts > latest.number ? latest.number : ts;
4523
+ };
4524
+ const fromBlock = toFromTs(snapshot, latest.number > 200000n ? latest.number - 200000n : 0n);
4525
+ const toBlock = toFromTs(deadline, latest.number);
4526
+ return {
4527
+ fromBlock,
4528
+ toBlock: toBlock < fromBlock ? latest.number : toBlock
4529
+ };
4530
+ }
4531
+ async function resolveNodePropertiesAddress(client, chainId, gov, args, deps) {
4532
+ if (deps?.readNodeProperties) {
4533
+ return deps.readNodeProperties();
4534
+ }
4535
+ if (isConfiguredAddress(args.nodeProperties)) {
4536
+ return viem.getAddress(args.nodeProperties);
4537
+ }
4538
+ let ve = isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : votingEscrowAddressOnEvmChain(chainId);
4539
+ if (!ve) {
4540
+ try {
4541
+ const token = await client.readContract({
4542
+ address: gov,
4543
+ abi: snapshotDeadlineAbi,
4544
+ functionName: "token"
4545
+ });
4546
+ ve = isConfiguredAddress(token) ? viem.getAddress(token) : null;
4547
+ } catch {
4548
+ ve = null;
4549
+ }
4550
+ }
4551
+ if (!ve) return null;
4552
+ try {
4553
+ const np = await client.readContract({
4554
+ address: ve,
4555
+ abi: veNodePropertiesAbi,
4556
+ functionName: "nodeProperties"
4557
+ });
4558
+ return isConfiguredAddress(np) ? viem.getAddress(np) : null;
4559
+ } catch {
4560
+ return null;
4561
+ }
4562
+ }
4563
+ async function readAttachmentsForVoters(client, nodeProperties, voters, deps) {
4564
+ const out = /* @__PURE__ */ new Map();
4565
+ await Promise.all(
4566
+ voters.map(async (raw) => {
4567
+ const keyGen = viem.getAddress(raw);
4568
+ try {
4569
+ const tokenId = deps?.readAttachedTokenId ? await deps.readAttachedTokenId(keyGen) : await client.readContract({
4570
+ address: nodeProperties,
4571
+ abi: nodePropsAttachAbi,
4572
+ functionName: "attachedTokenId",
4573
+ args: [keyGen]
4574
+ });
4575
+ if (tokenId > 0n) {
4576
+ let nodeInfo;
4577
+ try {
4578
+ const raw2 = deps?.readNodeInfo ? await deps.readNodeInfo(tokenId, keyGen) : await client.readContract({
4579
+ address: nodeProperties,
4580
+ abi: nodeInfoAbi,
4581
+ functionName: "nodeInfo",
4582
+ args: [tokenId, keyGen]
4583
+ });
4584
+ nodeInfo = normalizeNodeInfo(raw2);
4585
+ } catch {
4586
+ }
4587
+ out.set(keyGen.toLowerCase(), {
4588
+ attached: true,
4589
+ attachedTokenId: tokenId.toString(),
4590
+ ...nodeInfo ? { nodeInfo } : {}
4591
+ });
4592
+ } else {
4593
+ out.set(keyGen.toLowerCase(), { attached: false });
4594
+ }
4595
+ } catch {
4596
+ out.set(keyGen.toLowerCase(), { attached: false });
4597
+ }
4598
+ })
4599
+ );
4600
+ return out;
4601
+ }
4602
+ async function collectLogsInRange(client, gov, fromBlock, toBlock, deps) {
4603
+ const logs = [];
4604
+ let chunks = 0;
4605
+ let truncated = false;
4606
+ for (let start = fromBlock; start <= toBlock; start += LOG_CHUNK_BLOCKS) {
4607
+ if (chunks >= MAX_LOG_CHUNKS) {
4608
+ truncated = true;
4609
+ break;
4610
+ }
4611
+ const end = start + LOG_CHUNK_BLOCKS - 1n > toBlock ? toBlock : start + LOG_CHUNK_BLOCKS - 1n;
4612
+ const args = {
4613
+ address: gov,
4614
+ events: [VOTE_CAST_EVENT, VOTE_CAST_WITH_PARAMS_EVENT],
4615
+ fromBlock: start,
4616
+ toBlock: end
4617
+ };
4618
+ const chunk = deps?.getLogs ? await deps.getLogs(args) : await client.getLogs(args);
4619
+ logs.push(...chunk);
4620
+ chunks += 1;
4621
+ }
4622
+ return { logs, truncated };
4623
+ }
4624
+ async function continuumDaoFetchProposalVotes(args, deps) {
4625
+ const chainId = Number(args.chainId);
4626
+ const resolved = await resolveGovernorProposalRef(args.proposalId);
4627
+ const gov = requireGovernor(chainId, resolved.governorAddress ?? args.governor);
4628
+ const client = publicClient2(args.rpcUrl, chainId);
4629
+ const latest = deps?.getBlock ? await deps.getBlock() : await client.getBlock();
4630
+ const latestHead = { number: latest.number, timestamp: latest.timestamp };
4631
+ let fromBlock;
4632
+ let toBlock;
4633
+ if (args.fromBlock != null || args.toBlock != null) {
4634
+ fromBlock = args.fromBlock != null ? BigInt(String(args.fromBlock)) : 0n;
4635
+ toBlock = args.toBlock != null ? BigInt(String(args.toBlock)) : latestHead.number;
4636
+ } else {
4637
+ const window = await readWindow(client, gov, resolved.proposalId, latestHead, deps);
4638
+ fromBlock = window.fromBlock;
4639
+ toBlock = window.toBlock;
4640
+ }
4641
+ let optionLabels;
4642
+ let title;
4643
+ let configuration;
4644
+ if (resolved.catalogId != null) {
4645
+ try {
4646
+ const fetched = await (deps?.fetchProposal ?? continuumDaoFetchProposal)({
4647
+ id: resolved.catalogId
4648
+ });
4649
+ optionLabels = backendOptionLabels(fetched.proposal);
4650
+ title = backendTitle(fetched.proposal);
4651
+ configuration = backendConfiguration(fetched.proposal);
4652
+ } catch {
4653
+ }
4654
+ }
4655
+ let logs = [];
4656
+ let truncated = false;
4657
+ let logNote;
4658
+ try {
4659
+ const collected = await collectLogsInRange(client, gov, fromBlock, toBlock, deps);
4660
+ logs = collected.logs;
4661
+ truncated = collected.truncated;
4662
+ } catch (error) {
4663
+ const message = error instanceof Error ? error.message : String(error);
4664
+ logNote = `Could not read Governor VoteCast logs: ${message}. Load official etherscan and call get_logs on the governor if the operator chooses it.`;
4665
+ }
4666
+ let votes = collectVotesFromLogs(logs, resolved.proposalId, chainId, optionLabels).slice(
4667
+ 0,
4668
+ MAX_VOTES
4669
+ );
4670
+ if (votes.length === MAX_VOTES) truncated = true;
4671
+ let nodeProperties;
4672
+ let attachNote;
4673
+ try {
4674
+ const np = await resolveNodePropertiesAddress(client, chainId, gov, args, deps);
4675
+ if (np) {
4676
+ nodeProperties = np;
4677
+ const unique = [...new Set(votes.map((vote) => vote.voter))];
4678
+ const attachments = await readAttachmentsForVoters(client, np, unique, deps);
4679
+ votes = applyNodeAttachments(votes, attachments);
4680
+ } else {
4681
+ attachNote = "Could not resolve NodeProperties (VotingEscrow.nodeProperties after attachNodeFor). Pass nodeProperties if the operator has it.";
4682
+ }
4683
+ } catch (error) {
4684
+ const message = error instanceof Error ? error.message : String(error);
4685
+ attachNote = `Could not read NodeProperties.attachedTokenId: ${message}.`;
4686
+ }
4687
+ const summary = summarizeProposalVotes(votes);
4688
+ const note = [
4689
+ summary.note,
4690
+ logNote,
4691
+ attachNote,
4692
+ truncated ? "Result truncated; narrow fromBlock/toBlock if you need the rest." : ""
4693
+ ].filter(Boolean).join(" ");
4694
+ return {
4695
+ proposalId: resolved.proposalId.toString(),
4696
+ ...resolved.catalogId != null ? { catalogId: resolved.catalogId } : {},
4697
+ governor: gov,
4698
+ ...nodeProperties ? { nodeProperties } : {},
4699
+ ...title ? { title } : {},
4700
+ ...configuration ? { configuration } : {},
4701
+ fromBlock: fromBlock.toString(),
4702
+ toBlock: toBlock.toString(),
4703
+ truncated,
4704
+ votes,
4705
+ voterCount: summary.voterCount,
4706
+ attachedVoterCount: summary.attachedVoterCount,
4707
+ forWeight: summary.forWeight,
4708
+ againstWeight: summary.againstWeight,
4709
+ abstainWeight: summary.abstainWeight,
4710
+ note
4711
+ };
4712
+ }
4713
+
4714
+ // src/protocols/evm/continuum-dao/index.ts
4256
4715
  init_forum();
4257
4716
  var continuumDaoProtocolModule = {
4258
4717
  id: exports.CONTINUUM_DAO_PROTOCOL_ID,
@@ -4289,6 +4748,7 @@ var continuumDaoProtocolModule = {
4289
4748
  { id: "continuum-dao.cancel", protocolId: exports.CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Cancel a proposal", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { proposalId: { type: "string", required: true, description: "Catalog id or governor proposal hash" } } },
4290
4749
  { id: "continuum-dao.fetch-live-proposals", protocolId: exports.CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "List live/pending/succeeded proposals with on-chain state overlay", commonParams: [], params: { chainId: { type: "number", required: false, description: "Linea chain id" }, rpcUrl: { type: "string", required: false, description: "Linea RPC for state overlay" } } },
4291
4750
  { id: "continuum-dao.explain-proposal", protocolId: exports.CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Deconstruct a proposal into a readable briefing (actions, C3, no-ops, risks)", commonParams: [], params: { id: { type: "string", required: false, description: "Backend proposal id" }, onchainId: { type: "string", required: false, description: "Governor proposal id" } } },
4751
+ { id: "continuum-dao.fetch-proposal-votes", protocolId: exports.CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Who voted on a proposal and how (VoteCast / VoteCastWithParams)", commonParams: [], params: { proposalId: { type: "string", required: true, description: "Catalog id or governor proposal hash" } } },
4292
4752
  { id: "continuum-dao.forum-sign-in", protocolId: exports.CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "EIP-712 forum login (veCTM gate first)", commonParams: ["keyGen", "purposeText"], params: { nodeKey: { type: "string", required: false, description: "Attached node key" }, username: { type: "string", required: false, description: "Username when there is no node key" } } },
4293
4753
  { id: "continuum-dao.forum-sign-in-eligible", protocolId: exports.CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Check veCTM forum login eligibility", commonParams: [], params: { address: { type: "string", required: true, description: "KeyGen or wallet address" } } },
4294
4754
  { id: "continuum-dao.forum-sign-out", protocolId: exports.CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Revoke forum ticket and sessions", commonParams: [], params: { ticket: { type: "string", required: true, description: "Forum write ticket" } } },
@@ -4305,6 +4765,7 @@ var continuumDaoProtocolModule = {
4305
4765
  registerProtocolModule(continuumDaoProtocolModule);
4306
4766
 
4307
4767
  exports.ATTACH_VECTM_SIGNATURE = ATTACH_VECTM_SIGNATURE;
4768
+ exports.BRAVO_SUPPORT_LABELS = BRAVO_SUPPORT_LABELS;
4308
4769
  exports.CONTINUUM_DAO_APPROVE_FALLBACK = CONTINUUM_DAO_APPROVE_FALLBACK;
4309
4770
  exports.CONTINUUM_DAO_ATTACH_FALLBACK = CONTINUUM_DAO_ATTACH_FALLBACK;
4310
4771
  exports.CONTINUUM_DAO_CREATE_LOCK_FALLBACK = CONTINUUM_DAO_CREATE_LOCK_FALLBACK;
@@ -4320,7 +4781,10 @@ exports.CONTINUUM_DAO_TRANSFER_FALLBACK = CONTINUUM_DAO_TRANSFER_FALLBACK;
4320
4781
  exports.CONTINUUM_DAO_WITHDRAW_FALLBACK = CONTINUUM_DAO_WITHDRAW_FALLBACK;
4321
4782
  exports.INCREASE_UNLOCK_TIME_SIGNATURE = INCREASE_UNLOCK_TIME_SIGNATURE;
4322
4783
  exports.VECTM_NODE_INFO_TUPLE = VECTM_NODE_INFO_TUPLE;
4784
+ exports.VOTE_CAST_EVENT = VOTE_CAST_EVENT;
4785
+ exports.VOTE_CAST_WITH_PARAMS_EVENT = VOTE_CAST_WITH_PARAMS_EVENT;
4323
4786
  exports.applyForumSectionCheck = applyForumSectionCheck;
4787
+ exports.applyNodeAttachments = applyNodeAttachments;
4324
4788
  exports.assertContinuumDaoForumDeleteInput = assertContinuumDaoForumDeleteInput;
4325
4789
  exports.assertContinuumDaoForumMarkReadInput = assertContinuumDaoForumMarkReadInput;
4326
4790
  exports.assertContinuumDaoForumRecentInput = assertContinuumDaoForumRecentInput;
@@ -4340,6 +4804,7 @@ exports.assertSplitExtracted = assertSplitExtracted;
4340
4804
  exports.assertTransfer = assertTransfer;
4341
4805
  exports.assertUndelegateNotAlreadySelf = assertUndelegateNotAlreadySelf;
4342
4806
  exports.assertWithdraw = assertWithdraw;
4807
+ exports.bravoSupportLabel = bravoSupportLabel;
4343
4808
  exports.bucketGovernorState = bucketGovernorState;
4344
4809
  exports.buildBravoProposalArgs = buildBravoProposalArgs;
4345
4810
  exports.buildDeltaProposalArgs = buildDeltaProposalArgs;
@@ -4366,6 +4831,7 @@ exports.buildEvmMultisignBodyContinuumDaoTransfer = buildEvmMultisignBodyContinu
4366
4831
  exports.buildEvmMultisignBodyContinuumDaoUndelegate = buildEvmMultisignBodyContinuumDaoUndelegate;
4367
4832
  exports.buildEvmMultisignBodyContinuumDaoWithdraw = buildEvmMultisignBodyContinuumDaoWithdraw;
4368
4833
  exports.buildFoundryProposalSimulatePlan = buildFoundryProposalSimulatePlan;
4834
+ exports.collectVotesFromLogs = collectVotesFromLogs;
4369
4835
  exports.computeLiquidationAmounts = computeLiquidationAmounts;
4370
4836
  exports.continuumDaoComposeProposalAction = continuumDaoComposeProposalAction;
4371
4837
  exports.continuumDaoDeployment = continuumDaoDeployment;
@@ -4376,6 +4842,7 @@ exports.continuumDaoFetchProposal = continuumDaoFetchProposal;
4376
4842
  exports.continuumDaoFetchProposalCount = continuumDaoFetchProposalCount;
4377
4843
  exports.continuumDaoFetchProposalState = continuumDaoFetchProposalState;
4378
4844
  exports.continuumDaoFetchProposalVoteEligibility = continuumDaoFetchProposalVoteEligibility;
4845
+ exports.continuumDaoFetchProposalVotes = continuumDaoFetchProposalVotes;
4379
4846
  exports.continuumDaoFetchProposals = continuumDaoFetchProposals;
4380
4847
  exports.continuumDaoFetchVotingPower = continuumDaoFetchVotingPower;
4381
4848
  exports.continuumDaoForumCreateIdea = continuumDaoForumCreateIdea;
@@ -4412,19 +4879,23 @@ exports.continuumDaoSignatureText = continuumDaoSignatureText;
4412
4879
  exports.continuumDaoSimulateProposal = continuumDaoSimulateProposal;
4413
4880
  exports.continuumDaoTechnocoreBind = continuumDaoTechnocoreBind;
4414
4881
  exports.ctmTokenAddressOnEvmChain = ctmTokenAddressOnEvmChain;
4882
+ exports.decodeDeltaVoteParams = decodeDeltaVoteParams;
4415
4883
  exports.encodeContinuumDaoNodeInfo = encodeContinuumDaoNodeInfo;
4416
4884
  exports.encodeDeltaMetadata = encodeDeltaMetadata;
4417
4885
  exports.encodeDeltaVoteParams = encodeDeltaVoteParams;
4418
4886
  exports.encodeGovActionCalldata = encodeGovActionCalldata;
4887
+ exports.estimateBlockAtTimestamp = estimateBlockAtTimestamp;
4419
4888
  exports.executableGovBatches = executableGovBatches;
4420
4889
  exports.expectedForumSectionForProposalType = expectedForumSectionForProposalType;
4421
4890
  exports.explainGovAction = explainGovAction;
4422
4891
  exports.explainProposalRecord = explainProposalRecord;
4892
+ exports.explorerBase = explorerBase;
4423
4893
  exports.floorUnlockTimeSeconds = floorUnlockTimeSeconds;
4424
4894
  exports.formatProposalSimulationFailure = formatProposalSimulationFailure;
4425
4895
  exports.foundryPlanHasSequentialExecute = foundryPlanHasSequentialExecute;
4426
4896
  exports.foundryScriptContainsCall = foundryScriptContainsCall;
4427
4897
  exports.governorAddressOnEvmChain = governorAddressOnEvmChain;
4898
+ exports.howVoted = howVoted;
4428
4899
  exports.isConfiguredAddress = isConfiguredAddress;
4429
4900
  exports.isContinuumDaoCommitteeProposer = isContinuumDaoCommitteeProposer;
4430
4901
  exports.isContinuumDaoCtmOnAssetsChain = isContinuumDaoCtmOnAssetsChain;
@@ -4440,8 +4911,10 @@ exports.isRemoteC3Action = isRemoteC3Action;
4440
4911
  exports.isVotingEscrowContinuumName = isVotingEscrowContinuumName;
4441
4912
  exports.listContinuumDaoDefaultAssets = listContinuumDaoDefaultAssets;
4442
4913
  exports.looksLikeProposalCatalogId = looksLikeProposalCatalogId;
4914
+ exports.looksLikeUnixSeconds = looksLikeUnixSeconds;
4443
4915
  exports.maxLockDurationSeconds = maxLockDurationSeconds;
4444
4916
  exports.normalizeGovConfiguration = normalizeGovConfiguration;
4917
+ exports.normalizeNodeInfo = normalizeNodeInfo;
4445
4918
  exports.preflightAttachVeCtm = preflightAttachVeCtm;
4446
4919
  exports.preflightCreateLock = preflightCreateLock;
4447
4920
  exports.preflightDelegate = preflightDelegate;
@@ -4464,6 +4937,7 @@ exports.resolveAttachGroupId = resolveAttachGroupId;
4464
4937
  exports.resolveGovernorProposalRef = resolveGovernorProposalRef;
4465
4938
  exports.resolveProposalCatalogRange = resolveProposalCatalogRange;
4466
4939
  exports.summarizeLiveProposals = summarizeLiveProposals;
4940
+ exports.summarizeProposalVotes = summarizeProposalVotes;
4467
4941
  exports.throwIfProposalSimulationFailed = throwIfProposalSimulationFailed;
4468
4942
  exports.validateAbiParameter = validateAbiParameter;
4469
4943
  exports.votingEscrowAddressOnEvmChain = votingEscrowAddressOnEvmChain;