DeFiCh/jellyfish

View on GitHub
apps/whale-api/src/module.api/governance.service.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import {
  ListProposalsStatus,
  ListProposalsType,
  MasternodeType,
  ProposalInfo,
  ProposalStatus,
  ProposalType,
  VoteResult
} from '@defichain/jellyfish-api-core/dist/category/governance'
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'
import { RpcApiError } from '@defichain/jellyfish-api-core'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ApiPagedResponse } from './_core/api.paged.response'
import { PaginationQuery } from './_core/api.query'
import { NotFoundApiException } from './_core/api.error'
import {
  GovernanceProposal,
  GovernanceProposalStatus,
  GovernanceProposalType,
  ProposalVoteResultType,
  ProposalVotesResult
} from '@defichain/whale-api-client/dist/api/governance'

@Injectable()
export class GovernanceService {
  constructor (private readonly client: JsonRpcClient) {}

  async listGovernanceProposal (
    query: PaginationQuery = {
      size: 30
    },
    status?: ListProposalsStatus,
    type?: ListProposalsType,
    cycle?: number,
    all: boolean = false
  ): Promise<ApiPagedResponse<GovernanceProposal>> {
    const next = query.next !== undefined ? String(query.next) : undefined
    const size = all ? 0 : Math.min(query.size, 30) // blockchain by design to return all records when limit = 0
    const list = await this.client.governance.listGovProposals({
      type,
      status,
      cycle,
      pagination: {
        start: next,
        limit: size
      }
    })

    const proposals = list.map((proposal: ProposalInfo) => {
      return this.mapGovernanceProposal(proposal)
    })

    return ApiPagedResponse.of(proposals, size, (proposal) => {
      return proposal.proposalId
    })
  }

  async getGovernanceProposal (id: string): Promise<GovernanceProposal> {
    try {
      const proposal = await this.client.governance.getGovProposal(id)
      return this.mapGovernanceProposal(proposal)
    } catch (err) {
      if (
        err instanceof RpcApiError &&
        (err?.payload?.message === `Proposal <${id}> not found` ||
          err?.payload?.message.includes('proposalId must be of length 64'))
      ) {
        throw new NotFoundApiException('Unable to find proposal')
      } else {
        throw new BadRequestException(err)
      }
    }
  }

  async getGovernanceProposalVotes (
    query: PaginationQuery = {
      size: 30
    },
    id: string,
    masternode: MasternodeType | string = MasternodeType.MINE,
    cycle: number = 0,
    all?: boolean
  ): Promise<ApiPagedResponse<ProposalVotesResult>> {
    try {
      const next = query.next !== undefined ? Number(query.next) : undefined
      const size = all === true ? 0 : Math.min(query.size, 30) // blockchain by design to return all records when limit = 0
      const list = await this.client.governance.listGovProposalVotes({
        proposalId: id,
        masternode,
        cycle,
        pagination: {
          start: next,
          limit: size
        }
      })
      const votes = list.map((v) => ({
        ...v,
        vote: mapGovernanceProposalVoteResult(v.vote)
      }))

      return ApiPagedResponse.of(votes, size, () => {
        if (next === undefined) {
          return (votes.length - 1).toString()
        }
        return (next + votes.length).toString()
      })
    } catch (err) {
      if (
        err instanceof RpcApiError &&
        (err?.payload?.message === `Proposal <${id}> not found` ||
          err?.payload?.message.includes('proposalId must be of length 64'))
      ) {
        throw new NotFoundApiException('Unable to find proposal')
      } else {
        throw new BadRequestException(err)
      }
    }
  }

  private mapGovernanceProposal (data: ProposalInfo): GovernanceProposal {
    return {
      ...data,
      type: mapGovernanceProposalType(data.type),
      status: mapGovernanceProposalStatus(data.status),
      amount: data.amount?.toFixed(8)
    }
  }
}

function mapGovernanceProposalType (type: ProposalType): GovernanceProposalType {
  switch (type) {
    case ProposalType.COMMUNITY_FUND_PROPOSAL:
      return GovernanceProposalType.COMMUNITY_FUND_PROPOSAL
    case ProposalType.VOTE_OF_CONFIDENCE:
      return GovernanceProposalType.VOTE_OF_CONFIDENCE
  }
}

function mapGovernanceProposalStatus (
  type: ProposalStatus
): GovernanceProposalStatus {
  switch (type) {
    case ProposalStatus.COMPLETED:
      return GovernanceProposalStatus.COMPLETED
    case ProposalStatus.REJECTED:
      return GovernanceProposalStatus.REJECTED
    case ProposalStatus.VOTING:
      return GovernanceProposalStatus.VOTING
  }
}

function mapGovernanceProposalVoteResult (type: VoteResult): ProposalVoteResultType {
  switch (type) {
    case VoteResult.YES:
      return ProposalVoteResultType.YES
    case VoteResult.NO:
      return ProposalVoteResultType.NO
    case VoteResult.NEUTRAL:
      return ProposalVoteResultType.NEUTRAL
    default:
      return ProposalVoteResultType.UNKNOWN
  }
}