UNPKG

@moonwell-fi/moonwell-sdk

Version:

TypeScript Interface for Moonwell

509 lines 23.2 kB
"use strict"; 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