@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
509 lines • 23.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getExtendedProposalData = exports.appendProposalExtendedData = exports.getCrossChainProposalData = exports.getProposalData = exports.getProposalsOnChainData = exports.formatApiProposalData = exports.isMultichainAware = exports.isMultichainProposal = exports.extractProposalSubtitle = exports.WORMHOLE_CONTRACT = void 0;
const axios_1 = __importDefault(require("axios"));
const last_js_1 = __importDefault(require("lodash/last.js"));
const chains_1 = require("viem/chains");
const index_js_1 = require("../../../common/index.js");
const index_js_2 = require("../../../environments/index.js");
const proposal_js_1 = require("../../../types/proposal.js");
const axiosWithRetry_js_1 = require("../../axiosWithRetry.js");
exports.WORMHOLE_CONTRACT = "0xc8e2b0cd52cf01b0ce87d389daa3d414d4ce29f3";
axios_1.default.defaults.timeout = 5_000;
const extractProposalSubtitle = (input) => {
const lines = input.split("\n");
const h1Line = lines.find((line) => line.startsWith("#"));
if (!h1Line) {
return input ? input.substring(0, 100) : "";
}
let result = h1Line.substring(1).trim();
const h2Index = result.indexOf("##");
if (h2Index !== -1) {
result = result.substring(0, h2Index).trim();
}
result = result.replace(/\\n/g, "").trim();
if (result.length > 80) {
result = `${result.substring(0, 80)}...`;
}
if (result.includes("Moonbeam")) {
result = result.replace("MIP-B", "MIP-M");
}
if (result.indexOf("MIP-B22: Gauntlet") >= 0) {
result = result.replace("MIP-B22", "MIP-B24");
}
if (result.indexOf("MIP-O01: Gauntlet") >= 0) {
result = result.replace("MIP-O01", "MIP-O03");
}
if (result.indexOf("MIP-M02: Upgrade") >= 0) {
result = result.replace("MIP-M02", "MIP-M03");
}
if (result.indexOf("MIP-R02: Upgrade") >= 0) {
result = result.replace("MIP-R02", "MIP-R03");
}
if (result.indexOf("Proposal: Onboard wstETH") >= 0) {
result = result.replace("Proposal:", "MIP-B08");
}
if (result.indexOf("Gauntlet's Moonriver Recommendations (2024-01-09)") >= 0) {
result = result.replace("Gauntlet", "MIP-R10: Gauntlet");
}
return result;
};
exports.extractProposalSubtitle = extractProposalSubtitle;
const isMultichainProposal = (targets) => {
return (targets?.some((target) => target.toLowerCase() === exports.WORMHOLE_CONTRACT.toLowerCase()) ?? false);
};
exports.isMultichainProposal = isMultichainProposal;
const isMultichainAware = (proposal, legacyArtemisMaxId) => (0, exports.isMultichainProposal)(proposal.targets) ||
(legacyArtemisMaxId > 0 && proposal.proposalId > legacyArtemisMaxId);
exports.isMultichainAware = isMultichainAware;
const formatApiProposalData = (apiProposal) => {
const forVotesNum = Number(apiProposal.forVotes);
const againstVotesNum = Number(apiProposal.againstVotes);
const abstainVotesNum = Number(apiProposal.abstainVotes);
const forVotes = new index_js_1.Amount(BigInt(Math.floor(forVotesNum * 1e18)), 18);
const againstVotes = new index_js_1.Amount(BigInt(Math.floor(againstVotesNum * 1e18)), 18);
const abstainVotes = new index_js_1.Amount(BigInt(Math.floor(abstainVotesNum * 1e18)), 18);
const totalVotesValue = forVotesNum + againstVotesNum + abstainVotesNum;
const totalVotes = new index_js_1.Amount(BigInt(Math.floor(totalVotesValue * 1e18)), 18);
const canceled = apiProposal.stateChanges?.some((sc) => sc.state === "CANCELED") ??
false;
const executed = apiProposal.stateChanges?.some((sc) => sc.state === "EXECUTED") ??
false;
const stateChanges = apiProposal.stateChanges?.map((sc) => ({
blockNumber: Number(sc.blockNumber),
transactionHash: sc.transactionHash,
state: sc.state,
chainId: sc.chainId,
timestamp: sc.timestamp !== undefined ? Number(sc.timestamp) : undefined,
})) || [];
const subtitle = (0, exports.extractProposalSubtitle)(apiProposal.description);
const title = `Proposal #${apiProposal.proposalId}`;
return {
forVotes,
againstVotes,
abstainVotes,
totalVotes,
canceled,
executed,
stateChanges,
title,
subtitle,
};
};
exports.formatApiProposalData = formatApiProposalData;
const LEGACY_ARTEMIS_MAX_ID_TTL_MS = 5 * 60 * 1000;
const legacyArtemisMaxIdCache = new Map();
const getLegacyArtemisMaxId = async (governanceEnvironment) => {
const governor = governanceEnvironment.contracts.governor;
if (!governor)
return 0;
const cached = legacyArtemisMaxIdCache.get(governanceEnvironment.chainId);
if (cached && Date.now() - cached.fetchedAt < LEGACY_ARTEMIS_MAX_ID_TTL_MS) {
return cached.value;
}
try {
const value = Number(await governor.read.proposalCount());
legacyArtemisMaxIdCache.set(governanceEnvironment.chainId, {
value,
fetchedAt: Date.now(),
});
return value;
}
catch (error) {
console.warn("Failed to fetch legacy governor proposalCount:", error);
return cached?.value ?? 0;
}
};
const getProposalsOnChainData = async (apiProposals, governanceEnvironment) => {
let quorum = 0n;
if (governanceEnvironment.contracts.governor) {
try {
quorum =
governanceEnvironment.chainId === 1284
? await governanceEnvironment.contracts.governor.read.quorumVotes()
: await governanceEnvironment.contracts.governor.read.getQuorum();
}
catch (error) {
console.warn("Failed to fetch quorum:", error);
}
}
const legacyArtemisMaxId = await getLegacyArtemisMaxId(governanceEnvironment);
const onChainDataList = await Promise.all(apiProposals.map(async (p) => {
const isMultichain = (0, exports.isMultichainAware)(p, legacyArtemisMaxId);
const governorContract = isMultichain
? governanceEnvironment.contracts.multichainGovernor
: governanceEnvironment.contracts.governor;
let state = 0;
let proposalData = null;
if (governorContract) {
try {
[state, proposalData] = await Promise.all([
governorContract.read.state([BigInt(p.proposalId)]),
governorContract.read.proposals([BigInt(p.proposalId)]),
]);
}
catch (error) {
console.warn("Failed to fetch state and proposalData:", error);
}
}
let eta = 0;
if (proposalData) {
const onChainEta = Number(proposalData[4]);
if (onChainEta === 0 && isMultichain && p.votingEndTime) {
eta = p.votingEndTime + 86400;
}
else {
eta = onChainEta;
}
}
else if (isMultichain && p.votingEndTime) {
eta = p.votingEndTime + 86400;
}
return { state, proposalData, eta, votesCollected: false, quorum };
}));
const votesCollectedList = await Promise.all(apiProposals.map(async (apiProposal) => {
const isMultichain = (0, exports.isMultichainAware)(apiProposal, legacyArtemisMaxId);
if (!isMultichain ||
!governanceEnvironment.contracts.multichainGovernor) {
return false;
}
const xcGovernanceSettings = governanceEnvironment.custom.governance;
if (!xcGovernanceSettings || xcGovernanceSettings.chainIds.length === 0) {
return false;
}
try {
const xcEnvironments = xcGovernanceSettings.chainIds
.map((chainId) => Object.values(index_js_2.publicEnvironments).find((env) => env.chainId === chainId))
.filter((env) => {
if (!env)
return false;
const hasWormhole = env.custom &&
"wormhole" in env.custom &&
env.custom.wormhole?.chainId;
const hasVoteCollector = env.contracts &&
"voteCollector" in env.contracts &&
env.contracts.voteCollector;
return !!(hasWormhole && hasVoteCollector);
});
if (xcEnvironments.length === 0) {
return false;
}
const votesCollectedChecks = await Promise.all(xcEnvironments.map(async (xcEnvironment) => {
try {
const wormholeChainId = xcEnvironment.custom?.wormhole
?.chainId;
if (!wormholeChainId)
return false;
const [forVotes, againstVotes, abstainVotes] = await governanceEnvironment.contracts.multichainGovernor.read.chainVoteCollectorVotes([wormholeChainId, BigInt(apiProposal.proposalId)]);
return forVotes > 0n || againstVotes > 0n || abstainVotes > 0n;
}
catch (error) {
return false;
}
}));
return (votesCollectedChecks.length > 0 &&
votesCollectedChecks.every((collected) => collected));
}
catch (error) {
console.warn("Failed to check votes collected status:", error);
return false;
}
}));
return onChainDataList.map((data, index) => ({
...data,
votesCollected: votesCollectedList[index] ?? false,
}));
};
exports.getProposalsOnChainData = getProposalsOnChainData;
const getProposalData = async (params) => {
try {
if (params.environment.contracts.governor) {
let count = 0n;
let quorum = 0n;
if (params.environment.chainId === chains_1.moonriver.id) {
[count, quorum] = await Promise.all([
params.environment.contracts.governor.read.proposalCount(),
params.environment.contracts.governor.read.getQuorum(),
]);
}
else {
[count, quorum] = await Promise.all([
params.environment.contracts.governor.read.proposalCount(),
params.environment.contracts.governor.read.quorumVotes(),
]);
}
if (params.id) {
if (BigInt(params.id) > count) {
return [];
}
}
const ids = params.id
? [BigInt(params.id)]
: Array.from({ length: Number(count) }, (_, i) => count - BigInt(i));
const proposalDataCall = Promise.all(ids.map((id) => params.environment.contracts.governor?.read.proposals([id])));
const proposalStateCall = Promise.all(ids.map((id) => params.environment.contracts.governor?.read.state([id])));
const [proposalsData, proposalsState] = await Promise.all([
proposalDataCall,
proposalStateCall,
]);
const proposals = proposalsData?.map((item, index) => {
const state = proposalsState?.[index];
const [id, proposer, eta, startTimestamp, endTimestamp, startBlock, forVotes, againstVotes, abstainVotes, totalVotes, canceled, executed,] = item;
const proposal = {
chainId: params.environment.chainId,
id: Number(id),
proposalId: Number(id),
proposer,
eta: Number(eta),
startTimestamp: Number(startTimestamp),
endTimestamp: Number(endTimestamp),
startBlock: Number(startBlock),
forVotes: new index_js_1.Amount(forVotes, 18),
againstVotes: new index_js_1.Amount(againstVotes, 18),
abstainVotes: new index_js_1.Amount(abstainVotes, 18),
totalVotes: new index_js_1.Amount(totalVotes, 18),
canceled,
executed,
quorum: new index_js_1.Amount(quorum, 18),
state,
};
return proposal;
});
return proposals;
}
else {
return [];
}
}
catch (error) {
console.warn(`[getProposalData] RPC failed for chain ${params.environment.chainId}:`, error);
params.environment.onError?.(error, {
source: "governance-proposals",
chainId: params.environment.chainId,
});
return [];
}
};
exports.getProposalData = getProposalData;
const getCrossChainProposalData = async (params) => {
try {
if (params.environment.contracts.governor) {
const xcGovernanceSettings = params.environment.custom.governance;
if (params.environment.contracts.multichainGovernor &&
xcGovernanceSettings &&
xcGovernanceSettings.chainIds.length > 0) {
const xcEnvironments = xcGovernanceSettings.chainIds
.map((chainId) => Object.values(index_js_2.publicEnvironments).find((env) => env.chainId === chainId))
.filter((xcEnvironment) => !!xcEnvironment)
.filter((xcEnvironment) => xcEnvironment.custom?.wormhole?.chainId &&
xcEnvironment.contracts.voteCollector);
const [xcCount, xcQuorum] = await Promise.all([
params.environment.contracts.multichainGovernor.read.proposalCount(),
params.environment.contracts.multichainGovernor.read.quorum(),
]);
if (params.id) {
params.id =
Number(params.id) -
(params.environment.custom?.governance?.proposalIdOffset || 0);
if (params.id < 0)
return [];
if (BigInt(params.id) > xcCount)
return [];
}
const ids = params.id
? [BigInt(params.id)]
: Array.from({ length: Number(xcCount) }, (_, i) => xcCount - BigInt(i));
const xcProposalsDataCall = Promise.all(ids.map((id) => params.environment.contracts.multichainGovernor?.read.proposals([
id,
])));
const xcProposalsStateCall = Promise.all(ids.map((id) => params.environment.contracts.multichainGovernor?.read.state([id])));
const xcVotesCall = Promise.all(xcEnvironments.map((xcEnvironment) => Promise.all(ids.map((id) => params.environment.contracts.multichainGovernor?.read.chainVoteCollectorVotes([xcEnvironment.custom.wormhole.chainId, id])))));
const [xcProposalsData, xcProposalsState, xcVotes] = await Promise.all([
xcProposalsDataCall,
xcProposalsStateCall,
xcVotesCall,
]);
const proposals = ids.map((xcId, proposalIndex) => {
const state = xcProposalsState?.[proposalIndex];
const id = Number(xcId) +
(params.environment.custom?.governance?.proposalIdOffset || 0);
const votesCollected = false;
const votes = xcVotes.reduce((prevVotes, currVotes) => {
const voteData = currVotes[proposalIndex];
if (!voteData) {
return prevVotes;
}
const forVotes = voteData[0] || 0n;
const againstVotes = voteData[1] || 0n;
const abstainVotes = voteData[2] || 0n;
const totalVotes = forVotes + againstVotes + abstainVotes;
return {
totalVotes: prevVotes.totalVotes + totalVotes,
forVotes: prevVotes.forVotes + forVotes,
againstVotes: prevVotes.againstVotes + againstVotes,
abstainVotes: prevVotes.abstainVotes + abstainVotes,
};
}, {
totalVotes: 0n,
forVotes: 0n,
againstVotes: 0n,
abstainVotes: 0n,
});
const proposalData = xcProposalsData?.[proposalIndex];
if (!proposalData) {
throw new Error(`Proposal data not found for index ${proposalIndex}`);
}
const [proposer, _voteSnapshotTimestamp, votingStartTime, votingEndTime, crossChainVoteCollectionEndTimestamp, voteSnapshotBlock, proposalForVotes, proposalAgainstVotes, proposalAbstainVotes, proposalTotalVotes, canceled, executed,] = proposalData;
const multichainState = proposal_js_1.MultichainProposalStateMapping[state];
const proposal = {
chainId: params.environment.chainId,
id,
proposalId: Number(xcId),
proposer,
eta: Number(crossChainVoteCollectionEndTimestamp),
startTimestamp: Number(votingStartTime),
endTimestamp: Number(votingEndTime),
startBlock: Number(voteSnapshotBlock),
forVotes: new index_js_1.Amount(proposalForVotes + votes.forVotes, 18),
againstVotes: new index_js_1.Amount(proposalAgainstVotes + votes.againstVotes, 18),
abstainVotes: new index_js_1.Amount(proposalAbstainVotes + votes.abstainVotes, 18),
totalVotes: new index_js_1.Amount(proposalTotalVotes + votes.totalVotes, 18),
canceled,
executed,
quorum: new index_js_1.Amount(xcQuorum, 18),
state: multichainState,
multichain: {
id: Number(xcId),
votesCollected,
},
};
return proposal;
});
return proposals;
}
else {
return [];
}
}
else {
return [];
}
}
catch (error) {
console.warn(`[getCrossChainProposalData] RPC failed for chain ${params.environment.chainId}:`, error);
params.environment.onError?.(error, {
source: "governance-proposals",
chainId: params.environment.chainId,
});
return [];
}
};
exports.getCrossChainProposalData = getCrossChainProposalData;
const appendProposalExtendedData = (proposals, extendedDatas) => {
proposals.forEach((proposal) => {
const extendedData = extendedDatas.find((item) => item.id === proposal.proposalId);
if (extendedData) {
proposal.title = extendedData.title;
proposal.calldatas = extendedData.calldatas;
proposal.description = extendedData.description;
proposal.signatures = extendedData.signatures;
proposal.stateChanges = extendedData.stateChanges.map((change) => ({
blockNumber: change.blockNumber,
transactionHash: change.transactionHash,
state: change.state,
chainId: proposal.chainId,
}));
proposal.subtitle = extendedData.subtitle;
proposal.targets = extendedData.targets;
}
});
};
exports.appendProposalExtendedData = appendProposalExtendedData;
const getExtendedProposalData = async (params) => {
const result = [];
let lastId = -1;
let shouldContinue = true;
const MAX_PAGES = 100;
let page = 0;
try {
while (shouldContinue && page < MAX_PAGES) {
page++;
const response = await (0, axiosWithRetry_js_1.postWithRetry)("https://ponder-eu2.moonwell.fi", {
query: `
query {
proposals(
limit: 1000,
orderDirection: "desc",
orderBy: "proposalId",
where: {
chainId: ${params.environment.chainId}
${params.id ? `, proposalId: ${params.id}` : lastId >= 0 ? `, proposalId_lt: ${lastId}` : ""}
}
) {
items {
id
proposalId
description
targets
calldatas
signatures
stateChanges(orderBy: "blockNumber") {
items {
txnHash
blockNumber
newState
}
}
}
}
}
`,
});
if (response.status === 200 && response.data?.data?.proposals) {
const proposals = response.data.data.proposals.items.map((item) => {
const extendedProposalData = {
id: item.proposalId,
title: `Proposal #${item.proposalId}`,
subtitle: (0, exports.extractProposalSubtitle)(item.description),
description: item.description,
calldatas: item.calldatas,
signatures: item.signatures,
stateChanges: item.stateChanges.items.map((change) => {
return {
blockNumber: change.blockNumber,
state: change.newState,
transactionHash: change.txnHash,
};
}),
targets: item.targets,
};
return extendedProposalData;
});
if (proposals.length < 1000 || proposals.length === 0) {
shouldContinue = false;
}
else {
lastId = (0, last_js_1.default)(proposals).id;
}
result.push(...proposals);
}
else {
shouldContinue = false;
}
}
}
catch (error) {
console.warn(`[getExtendedProposalData] Ponder failed for chain ${params.environment.chainId}:`, error);
params.environment.onError?.(error, {
source: "governance-proposals",
chainId: params.environment.chainId,
});
return result;
}
return result;
};
exports.getExtendedProposalData = getExtendedProposalData;
//# sourceMappingURL=common.js.map