UNPKG

@moonwell-fi/moonwell-sdk

Version:

TypeScript Interface for Moonwell

467 lines (388 loc) 12.2 kB
import type { Environment } from "../../environments/index.js"; import { getWithRetry } from "../axiosWithRetry.js"; /** * Base configuration for Governor API requests * The Governor API is the new governance indexer, accessed via governanceIndexerUrl */ const getGovernorApiUrl = (environment: Environment): string => { return environment.governanceIndexerUrl; }; /** * Paginated response type */ export type PaginatedResponse<T> = { results: T[]; nextCursor?: string; }; /** * Pagination options */ export type PaginationOptions = { limit?: number; cursor?: string | undefined; }; /** * Raw API response types matching Governor API spec */ export type ApiProposal = { id: string; chainId: number; proposalId: number; proposer: string; targets: string[]; values: string[]; calldatas: string[]; votingStartTime: number; votingEndTime: number; description: string; forVotes: string; againstVotes: string; abstainVotes: string; blockNumber: string; timestamp: number; transactionHash: string; stateChanges?: ApiProposalStateChange[]; }; export type ApiVote = { id: string; proposalId: string; voter: string; votes: string; // Raw vote weight (NOT scaled) voteValue: number; // 0=for, 1=against, 2=abstain blockNumber: string; chainId: number; timestamp: number; proposalForVotes: string; proposalAgainstVotes: string; proposalAbstainVotes: string; }; export type ApiVoter = { id: string; firstSeenBlock: string; firstSeenTimestamp: number; votedProposalIds: string[]; createdProposalIds: string[]; }; export type ApiProposalStateChange = { id: string; proposalId: string; state: "CREATED" | "CANCELED" | "EXECUTED" | "QUEUED" | "REBROADCASTED"; blockNumber: string; timestamp: number; transactionHash: string; forVotes: string; againstVotes: string; abstainVotes: string; chainId: number; // Chain ID where this state change occurred }; export type ApiVoteReceipt = { id: string; proposalId: string; voter: string; votes: string; voteValue: number; // 0=for, 1=against, 2=abstain blockNumber: string; chainId: number; timestamp: number; }; /** * Fetch proposals from Governor API */ export async function fetchProposals( environment: Environment, options?: PaginationOptions & { chainId?: number }, ): Promise<PaginatedResponse<ApiProposal>> { const baseUrl = getGovernorApiUrl(environment); const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.cursor) params.append("cursor", options.cursor); if (options?.chainId) params.append("chainId", options.chainId.toString()); const response = await getWithRetry<PaginatedResponse<ApiProposal>>( `${baseUrl}/api/v1/governor/proposals?${params.toString()}`, ); if (response.status !== 200 || !response.data) { throw new Error(`Failed to fetch proposals: ${response.statusText}`); } return response.data; } /** * Fetch all proposals (handles pagination internally) */ export async function fetchAllProposals( environment: Environment, options?: { chainId?: number }, ): Promise<ApiProposal[]> { const allProposals: ApiProposal[] = []; let cursor: string | undefined = undefined; do { const response = await fetchProposals(environment, { limit: 1000, ...(cursor && { cursor }), ...(options?.chainId && { chainId: options.chainId }), }); allProposals.push(...response.results); cursor = response.nextCursor; } while (cursor); return allProposals; } /** * Fetch a single proposal from Governor API */ export async function fetchProposal( environment: Environment, proposalId: string, ): Promise<ApiProposal> { const baseUrl = getGovernorApiUrl(environment); const response = await getWithRetry<ApiProposal>( `${baseUrl}/api/v1/governor/proposals/${proposalId}`, ); if (response.status !== 200 || !response.data) { throw new Error(`Failed to fetch proposal: ${response.statusText}`); } return response.data; } /** * Fetch votes for a proposal */ export async function fetchProposalVotes( environment: Environment, proposalId: string, options?: PaginationOptions, ): Promise<PaginatedResponse<ApiVote>> { const baseUrl = getGovernorApiUrl(environment); const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.cursor) params.append("cursor", options.cursor); const response = await getWithRetry<PaginatedResponse<ApiVote>>( `${baseUrl}/api/v1/governor/proposals/${proposalId}/votes?${params.toString()}`, ); if (response.status !== 200 || !response.data) { throw new Error(`Failed to fetch proposal votes: ${response.statusText}`); } return response.data; } /** * Fetch all votes for a proposal (handles pagination internally) */ export async function fetchAllProposalVotes( environment: Environment, proposalId: string, ): Promise<ApiVote[]> { const allVotes: ApiVote[] = []; let cursor: string | undefined = undefined; do { const response = await fetchProposalVotes(environment, proposalId, { limit: 1000, ...(cursor && { cursor }), }); allVotes.push(...response.results); cursor = response.nextCursor; } while (cursor); return allVotes; } /** * Fetch state changes for a proposal */ export async function fetchProposalStateChanges( environment: Environment, proposalId: string, options?: PaginationOptions, ): Promise<PaginatedResponse<ApiProposalStateChange>> { const baseUrl = getGovernorApiUrl(environment); const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.cursor) params.append("cursor", options.cursor); const response = await getWithRetry< PaginatedResponse<ApiProposalStateChange> >( `${baseUrl}/api/v1/governor/proposals/${proposalId}/state-changes?${params.toString()}`, ); if (response.status !== 200 || !response.data) { throw new Error( `Failed to fetch proposal state changes: ${response.statusText}`, ); } return response.data; } /** * Fetch all state changes for a proposal (handles pagination internally) */ export async function fetchAllProposalStateChanges( environment: Environment, proposalId: string, ): Promise<ApiProposalStateChange[]> { const allStateChanges: ApiProposalStateChange[] = []; let cursor: string | undefined = undefined; do { const response = await fetchProposalStateChanges(environment, proposalId, { limit: 1000, ...(cursor && { cursor }), }); allStateChanges.push(...response.results); cursor = response.nextCursor; } while (cursor); return allStateChanges; } /** * Fetch all voters (delegates) */ export async function fetchVoters( environment: Environment, options?: PaginationOptions, ): Promise<PaginatedResponse<ApiVoter>> { const baseUrl = getGovernorApiUrl(environment); const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.cursor) params.append("cursor", options.cursor); const endpoint = `${baseUrl}/api/v1/governor/voters?${params.toString()}`; try { const response = await getWithRetry<PaginatedResponse<ApiVoter>>(endpoint); if (response.status !== 200 || !response.data) { throw new Error(`Failed to fetch voters: ${response.statusText}`); } return response.data; } catch (error: any) { console.error("[Governor API] CORS/Network Error:", { message: error.message, code: error.code, endpoint, error: error.response || error, }); throw new Error( `CORS or Network error when fetching voters. The API at ${baseUrl} may need CORS headers configured. Original error: ${error.message}`, ); } } /** * Fetch all voters (handles pagination internally) */ export async function fetchAllVoters( environment: Environment, ): Promise<ApiVoter[]> { const allVoters: ApiVoter[] = []; let cursor: string | undefined = undefined; do { const response = await fetchVoters(environment, { limit: 100, ...(cursor && { cursor }), }); allVoters.push(...response.results); cursor = response.nextCursor; } while (cursor); return allVoters; } /** * Fetch a single voter */ export async function fetchVoter( environment: Environment, address: string, ): Promise<ApiVoter> { const baseUrl = getGovernorApiUrl(environment); const response = await getWithRetry<ApiVoter>( `${baseUrl}/api/v1/governor/voters/${address}`, ); if (response.status === 404) { throw new Error("Voter not found"); } if (response.status !== 200 || !response.data) { throw new Error(`Failed to fetch voter: ${response.statusText}`); } return response.data; } /** * Fetch proposals created by a specific address */ export async function fetchVoterProposals( environment: Environment, address: string, options?: PaginationOptions, ): Promise<PaginatedResponse<ApiProposal>> { const baseUrl = getGovernorApiUrl(environment); const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.cursor) params.append("cursor", options.cursor); const endpoint = `${baseUrl}/api/v1/governor/voters/${address}/proposals?${params.toString()}`; const response = await getWithRetry<PaginatedResponse<ApiProposal>>(endpoint); if (response.status !== 200 || !response.data) { throw new Error(`Failed to fetch voter proposals: ${response.statusText}`); } return response.data; } /** * Fetch all proposals created by a specific address (handles pagination internally) */ export async function fetchAllVoterProposals( environment: Environment, address: string, ): Promise<ApiProposal[]> { const allProposals: ApiProposal[] = []; let cursor: string | undefined = undefined; do { const response = await fetchVoterProposals(environment, address, { limit: 1000, ...(cursor && { cursor }), }); allProposals.push(...response.results); cursor = response.nextCursor; } while (cursor); return allProposals; } /** * Fetch votes cast by a specific address */ export async function fetchVoterVotes( environment: Environment, address: string, options?: PaginationOptions, ): Promise<PaginatedResponse<ApiVote>> { const baseUrl = getGovernorApiUrl(environment); const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.cursor) params.append("cursor", options.cursor); const endpoint = `${baseUrl}/api/v1/governor/voters/${address}/votes?${params.toString()}`; const response = await getWithRetry<PaginatedResponse<ApiVote>>(endpoint); if (response.status !== 200 || !response.data) { throw new Error(`Failed to fetch voter votes: ${response.statusText}`); } return response.data; } /** * Fetch all votes cast by a specific address (handles pagination internally) */ export async function fetchAllVoterVotes( environment: Environment, address: string, ): Promise<ApiVote[]> { const allVotes: ApiVote[] = []; let cursor: string | undefined = undefined; do { const response = await fetchVoterVotes(environment, address, { limit: 1000, ...(cursor && { cursor }), }); allVotes.push(...response.results); cursor = response.nextCursor; } while (cursor); return allVotes; } /** * Fetch vote receipts for a specific user on a proposal * Returns all votes across all chains for multichain proposals */ export async function fetchUserVoteReceipt( environment: Environment, proposalId: string, voterAddress: string, ): Promise<ApiVoteReceipt[]> { const baseUrl = getGovernorApiUrl(environment); const response = await getWithRetry<ApiVoteReceipt[]>( `${baseUrl}/api/v1/governor/proposals/${proposalId}/vote/${voterAddress}`, ); if (response.status !== 200 || !response.data) { throw new Error( `Failed to fetch user vote receipt: ${response.statusText}`, ); } return response.data; }