@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.
- package/dist/agent/catalog.cjs +98 -9
- package/dist/agent/catalog.cjs.map +1 -1
- package/dist/agent/catalog.d.ts +342 -1
- package/dist/agent/catalog.js +96 -10
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/skills/continuum-dao/SKILL.md +2 -0
- package/dist/protocols/evm/continuum-dao/index.cjs +475 -1
- package/dist/protocols/evm/continuum-dao/index.cjs.map +1 -1
- package/dist/protocols/evm/continuum-dao/index.d.ts +174 -1
- package/dist/protocols/evm/continuum-dao/index.js +463 -3
- package/dist/protocols/evm/continuum-dao/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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;
|