@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
176 lines (154 loc) • 4.99 kB
text/typescript
import { moonbeam, moonriver } from "viem/chains";
import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
import { Amount, getEnvironmentFromArgs } from "../../../common/index.js";
import type { NetworkParameterType } from "../../../common/types.js";
import type { Chain, Environment } from "../../../environments/index.js";
import type { Proposal } from "../../../types/proposal.js";
import { fetchProposal } from "../governor-api-client.js";
import {
appendProposalExtendedData,
formatApiProposalData,
getCrossChainProposalData,
getExtendedProposalData,
getProposalData,
getProposalsOnChainData,
isMultichainProposal,
} from "./common.js";
export type GetProposalParameters<
environments,
network extends Chain | undefined,
> = NetworkParameterType<environments, network> & {
proposalId: number;
};
export type GetProposalReturnType = Promise<Proposal | undefined>;
export async function getProposal<
environments,
Network extends Chain | undefined,
>(
client: MoonwellClient,
args: GetProposalParameters<environments, Network>,
): GetProposalReturnType {
const { proposalId } = args;
const environment = getEnvironmentFromArgs(client, args);
if (!environment) {
return undefined;
}
if (
environment.chainId !== moonbeam.id &&
environment.chainId !== moonriver.id
) {
return undefined;
}
try {
if (environment.chainId === moonbeam.id) {
// Moonbeam: Use new Governor API
return await getMoonbeamProposal(environment, proposalId);
} else {
// Moonriver: Use old Ponder approach
return await getMoonriverProposal(environment, proposalId);
}
} catch (error) {
console.error(
`[getProposal] Error fetching proposal ${proposalId}:`,
error,
);
return undefined;
}
}
/**
* Fetch a single proposal for Moonbeam using the new Governor API
*/
async function getMoonbeamProposal(
governanceEnvironment: Environment,
proposalId: number,
): Promise<Proposal | undefined> {
const apiProposalId = `${proposalId}`;
const apiProposal = await fetchProposal(governanceEnvironment, apiProposalId);
const formattedData = formatApiProposalData(apiProposal);
const onChainDataList = await getProposalsOnChainData(
[apiProposal],
governanceEnvironment,
);
const onChainData = onChainDataList[0]!;
const isMultichain = isMultichainProposal(apiProposal.targets);
let proposalState = onChainData.state;
const now = Math.floor(Date.now() / 1000);
if (
proposalState === 0 &&
now >= apiProposal.votingStartTime &&
now <= apiProposal.votingEndTime
) {
proposalState = 1; // Active
}
if (formattedData.executed) {
proposalState = 7; // ProposalState.Executed
} else if (
isMultichain &&
onChainData.votesCollected &&
now > apiProposal.votingEndTime &&
proposalState < 5
) {
proposalState = 5; // ProposalState.Queued
}
const proposal: Proposal = {
id: apiProposal.proposalId,
chainId: apiProposal.chainId,
proposalId: apiProposal.proposalId,
proposer: apiProposal.proposer as `0x${string}`,
eta: onChainData.eta,
startTimestamp: apiProposal.votingStartTime,
endTimestamp: apiProposal.votingEndTime,
startBlock: Number(apiProposal.blockNumber),
forVotes: formattedData.forVotes,
againstVotes: formattedData.againstVotes,
abstainVotes: formattedData.abstainVotes,
totalVotes: formattedData.totalVotes,
canceled: formattedData.canceled,
executed: formattedData.executed,
quorum: new Amount(onChainData.quorum, 18),
state: proposalState,
// Extended data
title: formattedData.title,
subtitle: formattedData.subtitle,
description: apiProposal.description,
targets: apiProposal.targets,
calldatas: apiProposal.calldatas,
signatures: [],
stateChanges: formattedData.stateChanges,
environment: governanceEnvironment,
};
if (isMultichain) {
proposal.multichain = {
id: apiProposal.proposalId,
votesCollected: onChainData.votesCollected,
};
}
return proposal;
}
/**
* Fetch a single proposal for Moonriver using the old Ponder-based approach
*/
async function getMoonriverProposal(
governanceEnvironment: Environment,
proposalId: number,
): Promise<Proposal | undefined> {
const [_proposals, _xcProposals, _extendedDatas] = await Promise.all([
getProposalData({ environment: governanceEnvironment, id: proposalId }),
getCrossChainProposalData({
environment: governanceEnvironment,
id: proposalId,
}),
getExtendedProposalData({
environment: governanceEnvironment,
id: proposalId,
}),
]);
const proposals = [..._proposals, ..._xcProposals];
proposals.forEach((proposal) => {
proposal.environment = governanceEnvironment;
});
appendProposalExtendedData(proposals, _extendedDatas);
return proposals.find(
(p) => p.proposalId === proposalId || p.id === proposalId,
);
}