UNPKG

@moonwell-fi/moonwell-sdk

Version:

TypeScript Interface for Moonwell

293 lines (267 loc) 8.15 kB
import axios from "axios"; import { isAddress } from "viem"; import type { MoonwellClient } from "../../client/createMoonwellClient.js"; import { HttpRequestError } from "../../common/index.js"; import { type Environment, publicEnvironments, } from "../../environments/index.js"; import * as logger from "../../logger/console.js"; import type { Delegate } from "../../types/delegate.js"; export type GetDelegatesErrorType = HttpRequestError; export type GetDelegatesReturnType = Promise<Delegate[]>; /** * Returns a list of the delegates from the Moonwell Governance Forum * * https://forum.moonwell.fi/c/delegation-pitch/17 */ export async function getDelegates( client: MoonwellClient, ): GetDelegatesReturnType { let users: Delegate[] = []; const logId = logger.start("getDelegates", "Starting to get delegates..."); const getUsersPaginated = async (page = 0) => { const response = await axios.get<{ directory_items: { user: { id: number; username: string; name: string; avatar_template: string; title: string; user_fields: { "1": { value: string[] }; "2": { value: string[] }; "3": { value: string[] }; }; wallet_address: string; pitch_intro: string; pitch_link: string; }; }[]; meta: { total_rows_directory_items: number; load_more_directory_items: string; }; }>( `https://forum.moonwell.fi/directory_items.json?period=all&order=Delegate+Wallet+Address&user_field_ids=2%7C1%7C3&page=${page}`, ); if (response.status !== 200 || !response.data) { throw new HttpRequestError(response.statusText); } const results = response.data.directory_items .filter( (item) => item.user.user_fields["1"] !== undefined && item.user.user_fields["1"].value !== undefined && item.user.user_fields["1"].value[0] !== undefined && isAddress(item.user.user_fields["1"].value[0]) && item.user.user_fields["2"] !== undefined && item.user.user_fields["2"].value !== undefined && item.user.user_fields["2"].value[0] !== undefined, ) .map((item) => { const avatar = item.user.avatar_template.replace("{size}", "160"); const result: Delegate = { avatar: avatar.startsWith("/user_avatar") ? `https://dub1.discourse-cdn.com/flex017${avatar}` : avatar, name: item.user.username, wallet: item.user.user_fields["1"].value[0], pitch: { intro: item.user.user_fields["2"].value[0], url: item.user.user_fields["3"]?.value[0], }, }; return result; }); users = users.concat(results); const loadMore = response.data.directory_items.length > 0; if (loadMore) { await getUsersPaginated(page + 1); } }; await getUsersPaginated(); //Get how many proposals the delegate have voted for const proposals = await getDelegatesExtendedData({ users: users.map((r) => r.wallet), }); //Get delegate voting powers const envs = Object.values(client.environments as Environment[]).filter( (env) => env.contracts.views !== undefined, ); const votingPowers = await Promise.all( users.map(async (user) => Promise.all( envs.map((environment) => environment.contracts.views?.read.getUserVotingPower([ user.wallet as `0x${string}`, ]), ), ), ), ); logger.end(logId); users = users.map((user, index) => { let votingPower: { [chainId: string]: number; } = {}; const userVotingPowers = votingPowers[index]; if (userVotingPowers) { votingPower = envs.reduce( (prev, curr, reduceIndex) => { const { claimsVotes, stakingVotes, tokenVotes } = userVotingPowers[reduceIndex]!; const totalVotes = claimsVotes.delegatedVotingPower + stakingVotes.delegatedVotingPower + tokenVotes.delegatedVotingPower; return { ...prev, [curr.chainId]: Number(totalVotes / BigInt(10 ** 18)), }; }, {} as { [chainId: string]: number }, ); } const extended: Delegate = { ...user, proposals: proposals[user.wallet.toLowerCase()], votingPower, }; return extended; }); return users; } /** * Helper function to get how many proposals the delegates have created and voted */ const getDelegatesExtendedData = async (params: { users: string[]; }) => { const response = await axios.post<{ data: { proposers: { items: { id: string; proposals: { items: { proposalId: string; chainId: number; }[]; }; }[]; }; voters: { items: { id: string; votes: { items: { proposal: { chainId: number; }; }[]; }; }[]; }; }; }>(publicEnvironments.moonbeam.governanceIndexerUrl, { query: ` query { proposers(where: {id_in: [${params.users.map((r) => `"${r.toLowerCase()}"`).join(",")}]}) { items { id proposals(limit: 1000) { items { chainId proposalId } } } } voters(where: {id_in: [${params.users.map((r) => `"${r.toLowerCase()}"`).join(",")}]}) { items { id votes(limit: 1000) { items { voter proposal { chainId } } } } } } `, }); if (response.status === 200 && response.data?.data?.voters) { const voters = response?.data?.data?.voters?.items.reduce( (prev, curr) => { return { ...prev, [curr.id.toLowerCase()]: curr.votes.items.reduce( (prevVotes, currVotes) => { const previousVotes = prevVotes[currVotes.proposal.chainId] || 0; return { ...prevVotes, [currVotes.proposal.chainId]: previousVotes + 1, }; }, {} as { [chainId: string]: number }, ), }; }, {} as { [voter: string]: { [chainId: string]: number } }, ); const proposers = response?.data?.data?.proposers?.items.reduce( (prev, curr) => { return { ...prev, [curr.id.toLowerCase()]: curr.proposals.items.reduce( (prevVotes, currVotes) => { const previousProposed = prevVotes[currVotes.chainId] || 0; return { ...prevVotes, [currVotes.chainId]: previousProposed + 1, }; }, {} as { [chainId: string]: number }, ), }; }, {} as { [proposer: string]: { [chainId: string]: number } }, ); return params.users.reduce( (prev, curr) => { const proposalsCreated = proposers[curr.toLowerCase()]; const proposalsVoted = voters[curr.toLowerCase()]; const chains = [ ...Object.keys(proposalsCreated || {}), ...Object.keys(proposalsVoted || {}), ]; return { ...prev, [curr.toLowerCase()]: chains.reduce( (prevChain, currChain) => { return { ...prevChain, [currChain]: { created: proposalsCreated?.[currChain] || 0, voted: proposalsVoted?.[currChain] || 0, }, }; }, {} as { [chainId: string]: { created: number; voted: number } }, ), }; }, {} as { [user: string]: { [chainId: string]: { created: number; voted: number }; }; }, ); } return {}; };