From 6e3bfd0a153ce5760e8a4e60638017c24aec4986 Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sat, 25 Nov 2023 04:11:58 +0530 Subject: [PATCH 01/10] wip(gov): gov slice --- frontend/src/store/features/gov/govService.ts | 72 +++++++ frontend/src/store/features/gov/govSlice.ts | 191 ++++++++++++++++++ frontend/src/store/store.ts | 2 + frontend/src/types/gov.d.ts | 116 +++++++++++ 4 files changed, 381 insertions(+) create mode 100644 frontend/src/store/features/gov/govService.ts create mode 100644 frontend/src/store/features/gov/govSlice.ts create mode 100644 frontend/src/types/gov.d.ts diff --git a/frontend/src/store/features/gov/govService.ts b/frontend/src/store/features/gov/govService.ts new file mode 100644 index 000000000..abf6c4bd1 --- /dev/null +++ b/frontend/src/store/features/gov/govService.ts @@ -0,0 +1,72 @@ +import Axios, { AxiosResponse } from 'axios'; +import { convertPaginationToParams, cleanURL } from '../../../utils/util'; + +const proposalsURL = '/cosmos/gov/v1beta1/proposals'; +const proposalTallyURL = (id: string): string => + `/cosmos/gov/v1beta1/proposals/${id}/tally`; + +const voterVoteURL = (id: string, voter: string): string => + `/cosmos/gov/v1beta1/proposals/${id}/votes/${voter}`; + +const depositParamsURL = `/cosmos/gov/v1beta1/params/deposit`; + +const fetchProposals = ( + baseURL: string, + key: string | undefined, + limit: number | undefined, + status: number +): Promise> => { + let uri = `${cleanURL(baseURL)}${proposalsURL}`; + uri += `?proposal_status=${status}`; + + const params = convertPaginationToParams({ + key: key, + limit: limit, + }); + + if (params !== '') uri += `&${params}`; + return Axios.get(uri); +}; + +const fetchProposalTally = ( + baseURL: string, + proposalId: string +): Promise => { + let uri = `${cleanURL(baseURL)}${proposalTallyURL(proposalId)}`; + return Axios.get(uri); +}; + +const fetchVoterVote = ( + baseURL: string, + proposalId: string, + voter: string, + key: string | undefined, + limit: number | undefined +): Promise> => { + let uri = `${cleanURL(baseURL)}${voterVoteURL(proposalId, voter)}`; + const params = convertPaginationToParams({ + key: key, + limit: limit, + }); + if (params !== '') uri += `?${params}`; + return Axios.get(uri); +}; + +const fetchProposal = ( + baseURL: string, + proposalId: number +): Promise => + Axios.get(`${cleanURL(baseURL)}${proposalsURL}/${proposalId}`); + +const fetchDepositParams = (baseURL: string): Promise => + Axios.get(`${cleanURL(baseURL)}${depositParamsURL}`); + +const result = { + proposals: fetchProposals, + tally: fetchProposalTally, + votes: fetchVoterVote, + proposal: fetchProposal, + depositParams: fetchDepositParams, +}; + +export default result; diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts new file mode 100644 index 000000000..c06ef5b50 --- /dev/null +++ b/frontend/src/store/features/gov/govSlice.ts @@ -0,0 +1,191 @@ +'use client'; + +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import govService from './govService'; +import { cloneDeep } from 'lodash'; +import { AxiosError } from 'axios'; +import { ERR_UNKNOWN } from '@/utils/errors'; +import { TxStatus } from '@/types/enums'; + +interface Chain { + active: { + status: string; + errMsg: string; + proposals: ActiveProposal[]; + }; + votes: VotesData; + tally: ProposalTallyData; +} + +interface Chains { + [key: string]: Chain; +} + +interface GovState { + chains: Chains; + defaultState: Chain; +} + +const initialState: GovState = { + chains: {}, + defaultState: { + active: { + status: TxStatus.INIT, + errMsg: '', + proposals: [], + }, + votes: { + status: TxStatus.INIT, + errMsg: '', + proposals: {}, + }, + tally: { + status: TxStatus.INIT, + errMsg: '', + proposalTally: {}, + }, + }, +}; + +export const getProposalsInVoting = createAsyncThunk( + 'gov/active-proposals', + async (data: GetProposalsInVotingInputs, { rejectWithValue, dispatch }) => { + try { + const response = await govService.proposals( + data.baseURL, + data.key, + data.limit, + 2 + ); + + if (response?.data?.proposals?.length > 0) { + const proposals = response?.data?.proposals; + for (let i = 0; i < proposals.length; i++) { + dispatch( + getProposalTally({ + baseURL: data.baseURL, + proposalId: proposals[i].proposal_id, + chainID: data.chainID, + }) + ); + + dispatch( + getVotes({ + baseURL: data.baseURL, + proposalId: proposals[i].proposal_id, + voter: data.voter, + chainID: data.chainID, + }) + ); + } + } + + return { + chainID: data.chainID, + data: response.data, + }; + } catch (error) { + if (error instanceof AxiosError) return rejectWithValue(error.message); + return rejectWithValue(ERR_UNKNOWN); + } + } +); + +export const getVotes = createAsyncThunk( + 'gov/voter-votes', + async (data: GetVotesInputs) => { + const response = await govService.votes( + data.baseURL, + data.proposalId, + data.voter, + data.key, + data.limit + ); + + response.data.vote.proposal_id = data.proposalId; + + return { + chainID: data.chainID, + data: response.data, + }; + } +); + +export const getProposalTally = createAsyncThunk( + 'gov/proposal-tally', + async (data: GetProposalTallyInputs) => { + const response = await govService.tally(data.baseURL, data.proposalId); + + response.data.tally.proposal_id = data.proposalId; + + return { + chainID: data.chainID, + data: response.data, + }; + } +); + +export const govSlice = createSlice({ + name: 'gov', + initialState, + reducers: {}, + extraReducers: (builder) => { + // active proposals + builder + .addCase(getProposalsInVoting.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + if (!state.chains[chainID]) + state.chains[chainID] = cloneDeep(initialState.defaultState); + }) + .addCase(getProposalsInVoting.fulfilled, (state, action) => { + const chainID = action.payload?.chainID || ''; + if (chainID.length > 0) { + let result = { + status: 'idle', + errMsg: '', + proposals: action.payload?.data?.proposals, + }; + state.chains[chainID].active = result; + } + }) + .addCase(getProposalsInVoting.rejected, (state, action) => {}); + + // votes + builder + .addCase(getVotes.pending, () => {}) + .addCase(getVotes.fulfilled, (state, action) => { + const chainID = action.payload.chainID; + let result: VotesData = { + status: 'idle', + errMsg: '', + proposals: state.chains[chainID].votes?.proposals || {}, + }; + + result.proposals[action.payload?.data?.vote?.proposal_id] = + action.payload.data; + + state.chains[chainID].votes = result; + }) + .addCase(getVotes.rejected, () => {}); + + // tally + builder + .addCase(getProposalTally.pending, () => {}) + .addCase(getProposalTally.fulfilled, (state, action) => { + const chainID = action.payload.chainID; + let result = { + status: 'idle', + errMsg: '', + proposalTally: state.chains[chainID].tally?.proposalTally || {}, + }; + + result.proposalTally[action.payload?.data?.tally?.proposal_id] = + action.payload?.data.tally; + state.chains[chainID].tally = result; + }) + .addCase(getProposalTally.rejected, () => {}); + }, +}); + +export const {} = govSlice.actions; +export default govSlice.reducer; diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts index 86385af66..06061f865 100644 --- a/frontend/src/store/store.ts +++ b/frontend/src/store/store.ts @@ -8,6 +8,7 @@ import stakeSlice from './features/staking/stakeSlice'; import bankSlice from './features/bank/bankSlice'; import distributionSlice from './features/distribution/distributionSlice'; import authSlice from './features/auth/authSlice'; +import govSlice from './features/gov/govSlice'; export const store = configureStore({ reducer: { @@ -18,6 +19,7 @@ export const store = configureStore({ bank: bankSlice, auth: authSlice, distribution: distributionSlice, + gov: govSlice, }, }); diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts new file mode 100644 index 000000000..e96fdeec2 --- /dev/null +++ b/frontend/src/types/gov.d.ts @@ -0,0 +1,116 @@ +interface ActiveProposal { + proposal_id: string; + content: { + '@type': string; + title: string; + description: string; + changes?: { + subspace: string; + key: string; + value: string; + }[]; + }; + status: string; + final_tally_result: { + yes: string; + abstain: string; + no: string; + no_with_veto: string; + }; + submit_time: string; + deposit_end_time: string; + total_deposit: { + denom: string; + amount: string; + }[]; + voting_start_time: string; + voting_end_time: string; +} + +interface CosmosHubProposal { + status: string; + errMsg: string; + proposals: ActiveProposal[]; +} + +interface GovPagination { + next_key: string | undefined; + total: string; +} + +interface GetProposalsInVotingResponse { + proposals: ActiveProposal[]; + pagination: GovPagination; +} + +interface VoteOption { + option: string; + weight: string; +} + +interface Vote { + proposal_id: string; + voter: string; + option: string; + options: VoteOption[]; +} + +interface ProposalVote { + vote: Vote; +} + +interface VotesData { + status: string; + errMsg: string; + proposals: { + [key: string]: ProposalVote; + }; +} + +interface ProposalTally { + [key: string]: { + yes: string; + abstain: string; + no: string; + no_with_veto: string; + proposal_id: string; + }; +} + +interface GetProposalTallyResponse { + [key: string]: { + yes: string; + abstain: string; + no: string; + no_with_veto: string; + }; +} + +interface ProposalTallyData { + status: string; + errMsg: string; + proposalTally: ProposalTally; +} + +interface GetProposalsInVotingInputs { + baseURL: string; + chainID: string; + voter: string; + key?: string; + limit?: number; +} + +interface GetVotesInputs { + baseURL: string; + proposalId: string; + voter: string; + chainID: string; + key?: string; + limit?: number; +} + +interface GetProposalTallyInputs { + baseURL: string; + proposalId: string; + chainID: string; +} From c93a2068051a53cffd9437f0f62a89f5047ccd4b Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sat, 25 Nov 2023 12:59:31 +0530 Subject: [PATCH 02/10] wip(gov): refactor --- frontend/src/store/features/gov/govService.ts | 3 +- frontend/src/store/features/gov/govSlice.ts | 33 ++++++++++++++----- frontend/src/types/gov.d.ts | 12 +++---- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/frontend/src/store/features/gov/govService.ts b/frontend/src/store/features/gov/govService.ts index abf6c4bd1..e1c6446ee 100644 --- a/frontend/src/store/features/gov/govService.ts +++ b/frontend/src/store/features/gov/govService.ts @@ -1,5 +1,6 @@ import Axios, { AxiosResponse } from 'axios'; import { convertPaginationToParams, cleanURL } from '../../../utils/util'; +import { GetProposalsInVotingResponse, ProposalVote } from '@/types/gov'; const proposalsURL = '/cosmos/gov/v1beta1/proposals'; const proposalTallyURL = (id: string): string => @@ -32,7 +33,7 @@ const fetchProposalTally = ( baseURL: string, proposalId: string ): Promise => { - let uri = `${cleanURL(baseURL)}${proposalTallyURL(proposalId)}`; + const uri = `${cleanURL(baseURL)}${proposalTallyURL(proposalId)}`; return Axios.get(uri); }; diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index c06ef5b50..179312e1f 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -6,10 +6,18 @@ import { cloneDeep } from 'lodash'; import { AxiosError } from 'axios'; import { ERR_UNKNOWN } from '@/utils/errors'; import { TxStatus } from '@/types/enums'; +import { + ActiveProposal, + GetProposalTallyInputs, + GetProposalsInVotingInputs, + GetVotesInputs, + ProposalTallyData, + VotesData, +} from '@/types/gov'; interface Chain { active: { - status: string; + status: TxStatus; errMsg: string; proposals: ActiveProposal[]; }; @@ -140,23 +148,32 @@ export const govSlice = createSlice({ .addCase(getProposalsInVoting.fulfilled, (state, action) => { const chainID = action.payload?.chainID || ''; if (chainID.length > 0) { - let result = { - status: 'idle', + const result = { + status: TxStatus.IDLE, errMsg: '', proposals: action.payload?.data?.proposals, }; state.chains[chainID].active = result; } }) - .addCase(getProposalsInVoting.rejected, (state, action) => {}); + .addCase(getProposalsInVoting.rejected, (state, action) => { + const chainID = action.meta?.arg?.chainID; + const chainProposals = state.chains[chainID].active.proposals || {}; + const result = { + status: TxStatus.REJECTED, + errMsg: action.error.message || '', + proposals: chainProposals, + }; + state.chains[chainID].active = result; + }); // votes builder .addCase(getVotes.pending, () => {}) .addCase(getVotes.fulfilled, (state, action) => { const chainID = action.payload.chainID; - let result: VotesData = { - status: 'idle', + const result: VotesData = { + status: TxStatus.IDLE, errMsg: '', proposals: state.chains[chainID].votes?.proposals || {}, }; @@ -173,8 +190,8 @@ export const govSlice = createSlice({ .addCase(getProposalTally.pending, () => {}) .addCase(getProposalTally.fulfilled, (state, action) => { const chainID = action.payload.chainID; - let result = { - status: 'idle', + const result = { + status: TxStatus.IDLE, errMsg: '', proposalTally: state.chains[chainID].tally?.proposalTally || {}, }; diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts index e96fdeec2..6ffcba171 100644 --- a/frontend/src/types/gov.d.ts +++ b/frontend/src/types/gov.d.ts @@ -1,3 +1,5 @@ +import { TxStatus } from "./enums"; + interface ActiveProposal { proposal_id: string; content: { @@ -27,12 +29,6 @@ interface ActiveProposal { voting_end_time: string; } -interface CosmosHubProposal { - status: string; - errMsg: string; - proposals: ActiveProposal[]; -} - interface GovPagination { next_key: string | undefined; total: string; @@ -60,7 +56,7 @@ interface ProposalVote { } interface VotesData { - status: string; + status: TxStatus; errMsg: string; proposals: { [key: string]: ProposalVote; @@ -87,7 +83,7 @@ interface GetProposalTallyResponse { } interface ProposalTallyData { - status: string; + status: TxStatus; errMsg: string; proposalTally: ProposalTally; } From a455900c2e7218b7fb3f7dc20084977085b00485 Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sat, 25 Nov 2023 13:08:32 +0530 Subject: [PATCH 03/10] chore(gov) --- frontend/src/store/features/gov/govService.ts | 8 ++++---- frontend/src/store/features/gov/govSlice.ts | 4 ++-- frontend/src/types/gov.d.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/store/features/gov/govService.ts b/frontend/src/store/features/gov/govService.ts index e1c6446ee..4be9bfd94 100644 --- a/frontend/src/store/features/gov/govService.ts +++ b/frontend/src/store/features/gov/govService.ts @@ -3,10 +3,10 @@ import { convertPaginationToParams, cleanURL } from '../../../utils/util'; import { GetProposalsInVotingResponse, ProposalVote } from '@/types/gov'; const proposalsURL = '/cosmos/gov/v1beta1/proposals'; -const proposalTallyURL = (id: string): string => +const proposalTallyURL = (id: number): string => `/cosmos/gov/v1beta1/proposals/${id}/tally`; -const voterVoteURL = (id: string, voter: string): string => +const voterVoteURL = (id: number, voter: string): string => `/cosmos/gov/v1beta1/proposals/${id}/votes/${voter}`; const depositParamsURL = `/cosmos/gov/v1beta1/params/deposit`; @@ -31,7 +31,7 @@ const fetchProposals = ( const fetchProposalTally = ( baseURL: string, - proposalId: string + proposalId: number ): Promise => { const uri = `${cleanURL(baseURL)}${proposalTallyURL(proposalId)}`; return Axios.get(uri); @@ -39,7 +39,7 @@ const fetchProposalTally = ( const fetchVoterVote = ( baseURL: string, - proposalId: string, + proposalId: number, voter: string, key: string | undefined, limit: number | undefined diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index 179312e1f..f54ad7257 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -72,7 +72,7 @@ export const getProposalsInVoting = createAsyncThunk( dispatch( getProposalTally({ baseURL: data.baseURL, - proposalId: proposals[i].proposal_id, + proposalId: Number(proposals[i].proposal_id), chainID: data.chainID, }) ); @@ -80,7 +80,7 @@ export const getProposalsInVoting = createAsyncThunk( dispatch( getVotes({ baseURL: data.baseURL, - proposalId: proposals[i].proposal_id, + proposalId: Number(proposals[i].proposal_id), voter: data.voter, chainID: data.chainID, }) diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts index 6ffcba171..258ea5cb9 100644 --- a/frontend/src/types/gov.d.ts +++ b/frontend/src/types/gov.d.ts @@ -45,7 +45,7 @@ interface VoteOption { } interface Vote { - proposal_id: string; + proposal_id: number; voter: string; option: string; options: VoteOption[]; @@ -98,7 +98,7 @@ interface GetProposalsInVotingInputs { interface GetVotesInputs { baseURL: string; - proposalId: string; + proposalId: number; voter: string; chainID: string; key?: string; @@ -107,6 +107,6 @@ interface GetVotesInputs { interface GetProposalTallyInputs { baseURL: string; - proposalId: string; + proposalId: number; chainID: string; } From 86d6de86c6b8cf414673a3a752a42a75c5722631 Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sat, 25 Nov 2023 15:06:45 +0530 Subject: [PATCH 04/10] gov: Add getDepositProps reducer --- frontend/src/store/features/gov/govSlice.ts | 99 ++++++++++++++++++++- frontend/src/types/gov.d.ts | 13 ++- 2 files changed, 106 insertions(+), 6 deletions(-) diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index f54ad7257..93c3ab532 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -7,19 +7,28 @@ import { AxiosError } from 'axios'; import { ERR_UNKNOWN } from '@/utils/errors'; import { TxStatus } from '@/types/enums'; import { - ActiveProposal, + GovProposal, GetProposalTallyInputs, + GetProposalsInDepositInputs, GetProposalsInVotingInputs, GetVotesInputs, ProposalTallyData, VotesData, + GovPagination, } from '@/types/gov'; interface Chain { active: { status: TxStatus; errMsg: string; - proposals: ActiveProposal[]; + proposals: GovProposal[]; + pagination?: GovPagination; + }; + deposit: { + status: TxStatus; + errMsg: string; + proposals: GovProposal[]; + pagination?: GovPagination; }; votes: VotesData; tally: ProposalTallyData; @@ -34,6 +43,11 @@ interface GovState { defaultState: Chain; } +const EMPTY_PAGINATION = { + next_key: '', + total: '', +}; + const initialState: GovState = { chains: {}, defaultState: { @@ -41,6 +55,19 @@ const initialState: GovState = { status: TxStatus.INIT, errMsg: '', proposals: [], + pagination: { + next_key: '', + total: '', + }, + }, + deposit: { + status: TxStatus.INIT, + errMsg: '', + proposals: [], + pagination: { + next_key: '', + total: '', + }, }, votes: { status: TxStatus.INIT, @@ -55,6 +82,23 @@ const initialState: GovState = { }, }; +export const getProposalsInDeposit = createAsyncThunk( + 'gov/deposit-proposals', + async (data: GetProposalsInDepositInputs) => { + const response = await govService.proposals( + data.baseURL, + data?.key, + data?.limit, + 1 + ); + + return { + chainID: data.chainID, + data: response.data, + }; + } +); + export const getProposalsInVoting = createAsyncThunk( 'gov/active-proposals', async (data: GetProposalsInVotingInputs, { rejectWithValue, dispatch }) => { @@ -144,14 +188,23 @@ export const govSlice = createSlice({ const chainID = action.meta?.arg?.chainID; if (!state.chains[chainID]) state.chains[chainID] = cloneDeep(initialState.defaultState); + const chainProposals = state.chains[chainID].active.proposals || {}; + const result = { + status: TxStatus.PENDING, + errMsg: '', + proposals: chainProposals, + pagination: EMPTY_PAGINATION, + }; + state.chains[chainID].active = result; }) .addCase(getProposalsInVoting.fulfilled, (state, action) => { const chainID = action.payload?.chainID || ''; - if (chainID.length > 0) { + if (chainID.length) { const result = { status: TxStatus.IDLE, errMsg: '', proposals: action.payload?.data?.proposals, + pagination: action.payload?.data?.pagination, }; state.chains[chainID].active = result; } @@ -163,10 +216,50 @@ export const govSlice = createSlice({ status: TxStatus.REJECTED, errMsg: action.error.message || '', proposals: chainProposals, + pagination: EMPTY_PAGINATION, }; state.chains[chainID].active = result; }); + //proposals in deposit period + builder + .addCase(getProposalsInDeposit.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + if (!state.chains[chainID]) + state.chains[chainID] = cloneDeep(initialState.defaultState); + const chainProposals = state.chains[chainID].deposit.proposals || {}; + const result = { + status: TxStatus.PENDING, + errMsg: '', + proposals: chainProposals, + pagination: EMPTY_PAGINATION, + }; + state.chains[chainID].deposit = result; + }) + .addCase(getProposalsInDeposit.fulfilled, (state, action) => { + const chainID = action.payload?.chainID || ''; + if (chainID.length) { + const result = { + status: TxStatus.IDLE, + errMsg: '', + proposals: action.payload?.data?.proposals, + pagination: action.payload?.data?.pagination, + }; + state.chains[chainID].deposit = result; + } + }) + .addCase(getProposalsInDeposit.rejected, (state, action) => { + const chainID = action.meta?.arg?.chainID; + const chainProposals = state.chains[chainID].deposit.proposals || {}; + const result = { + status: TxStatus.REJECTED, + errMsg: action.error.message || '', + proposals: chainProposals, + pagination: EMPTY_PAGINATION, + }; + state.chains[chainID].deposit = result; + }); + // votes builder .addCase(getVotes.pending, () => {}) diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts index 258ea5cb9..5b84e5646 100644 --- a/frontend/src/types/gov.d.ts +++ b/frontend/src/types/gov.d.ts @@ -1,6 +1,6 @@ -import { TxStatus } from "./enums"; +import { TxStatus } from './enums'; -interface ActiveProposal { +interface GovProposal { proposal_id: string; content: { '@type': string; @@ -30,7 +30,7 @@ interface ActiveProposal { } interface GovPagination { - next_key: string | undefined; + next_key?: string; total: string; } @@ -96,6 +96,13 @@ interface GetProposalsInVotingInputs { limit?: number; } +interface GetProposalsInDepositInputs { + baseURL: string; + chainID: string; + key?: string; + limit?: number; +} + interface GetVotesInputs { baseURL: string; proposalId: number; From 86ebba2f465994ff905d6944533472c50cb4901f Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sat, 25 Nov 2023 19:06:19 +0530 Subject: [PATCH 05/10] wip(gov) --- frontend/src/store/features/gov/govSlice.ts | 188 +++++++++++++++----- frontend/src/types/gov.d.ts | 29 +++ 2 files changed, 177 insertions(+), 40 deletions(-) diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index 93c3ab532..10252f642 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -15,6 +15,8 @@ import { ProposalTallyData, VotesData, GovPagination, + DepositParams, + GetDepositParamsInputs, } from '@/types/gov'; interface Chain { @@ -30,6 +32,11 @@ interface Chain { proposals: GovProposal[]; pagination?: GovPagination; }; + depositParams: { + status: TxStatus; + errMsg: string; + params: DepositParams; + }; votes: VotesData; tally: ProposalTallyData; } @@ -69,6 +76,19 @@ const initialState: GovState = { total: '', }, }, + depositParams: { + status: TxStatus.INIT, + errMsg: '', + params: { + min_deposit: [ + { + denom: '', + amount: '', + }, + ], + max_deposit_period: '', + }, + }, votes: { status: TxStatus.INIT, errMsg: '', @@ -84,18 +104,51 @@ const initialState: GovState = { export const getProposalsInDeposit = createAsyncThunk( 'gov/deposit-proposals', - async (data: GetProposalsInDepositInputs) => { - const response = await govService.proposals( - data.baseURL, - data?.key, - data?.limit, - 1 - ); + async (data: GetProposalsInDepositInputs, { rejectWithValue, dispatch }) => { + try { + const response = await govService.proposals( + data.baseURL, + data?.key, + data?.limit, + 1 + ); - return { - chainID: data.chainID, - data: response.data, - }; + if (response?.data?.proposals?.length && data?.chainID?.length) { + dispatch( + getDepositParams({ + baseURL: data.baseURL, + chainID: data.chainID, + }) + ); + } + + return { + chainID: data.chainID, + data: response.data, + }; + } catch (error) { + if (error instanceof AxiosError) + return rejectWithValue({ message: error.message }); + return rejectWithValue({ message: ERR_UNKNOWN }); + } + } +); + +export const getDepositParams = createAsyncThunk( + 'gov/deposit-params', + async (data: GetDepositParamsInputs, { rejectWithValue }) => { + try { + const response = await govService.depositParams(data.baseURL); + + return { + chainID: data.chainID, + data: response.data, + }; + } catch (error) { + if (error instanceof AxiosError) + return rejectWithValue({ message: error.message }); + return rejectWithValue({ message: ERR_UNKNOWN }); + } } ); @@ -110,7 +163,7 @@ export const getProposalsInVoting = createAsyncThunk( 2 ); - if (response?.data?.proposals?.length > 0) { + if (response?.data?.proposals?.length) { const proposals = response?.data?.proposals; for (let i = 0; i < proposals.length; i++) { dispatch( @@ -137,43 +190,56 @@ export const getProposalsInVoting = createAsyncThunk( data: response.data, }; } catch (error) { - if (error instanceof AxiosError) return rejectWithValue(error.message); - return rejectWithValue(ERR_UNKNOWN); + if (error instanceof AxiosError) + return rejectWithValue({ message: error.message }); + return rejectWithValue({ message: ERR_UNKNOWN }); } } ); export const getVotes = createAsyncThunk( 'gov/voter-votes', - async (data: GetVotesInputs) => { - const response = await govService.votes( - data.baseURL, - data.proposalId, - data.voter, - data.key, - data.limit - ); + async (data: GetVotesInputs, { rejectWithValue }) => { + try { + const response = await govService.votes( + data.baseURL, + data.proposalId, + data.voter, + data.key, + data.limit + ); - response.data.vote.proposal_id = data.proposalId; + response.data.vote.proposal_id = data.proposalId; - return { - chainID: data.chainID, - data: response.data, - }; + return { + chainID: data.chainID, + data: response.data, + }; + } catch (error) { + if (error instanceof AxiosError) + return rejectWithValue({ message: error.message }); + return rejectWithValue({ message: ERR_UNKNOWN }); + } } ); export const getProposalTally = createAsyncThunk( 'gov/proposal-tally', - async (data: GetProposalTallyInputs) => { - const response = await govService.tally(data.baseURL, data.proposalId); + async (data: GetProposalTallyInputs, { rejectWithValue }) => { + try { + const response = await govService.tally(data.baseURL, data.proposalId); - response.data.tally.proposal_id = data.proposalId; + response.data.tally.proposal_id = data.proposalId; - return { - chainID: data.chainID, - data: response.data, - }; + return { + chainID: data.chainID, + data: response.data, + }; + } catch (error) { + if (error instanceof AxiosError) + return rejectWithValue({ message: error.message }); + return rejectWithValue({ message: ERR_UNKNOWN }); + } } ); @@ -211,10 +277,11 @@ export const govSlice = createSlice({ }) .addCase(getProposalsInVoting.rejected, (state, action) => { const chainID = action.meta?.arg?.chainID; + const payload = action.payload as { message: string }; const chainProposals = state.chains[chainID].active.proposals || {}; const result = { status: TxStatus.REJECTED, - errMsg: action.error.message || '', + errMsg: payload.message || '', proposals: chainProposals, pagination: EMPTY_PAGINATION, }; @@ -250,10 +317,11 @@ export const govSlice = createSlice({ }) .addCase(getProposalsInDeposit.rejected, (state, action) => { const chainID = action.meta?.arg?.chainID; + const payload = action.payload as { message: string }; const chainProposals = state.chains[chainID].deposit.proposals || {}; const result = { status: TxStatus.REJECTED, - errMsg: action.error.message || '', + errMsg: payload.message || '', proposals: chainProposals, pagination: EMPTY_PAGINATION, }; @@ -262,7 +330,10 @@ export const govSlice = createSlice({ // votes builder - .addCase(getVotes.pending, () => {}) + .addCase(getVotes.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].votes.status = TxStatus.PENDING; + }) .addCase(getVotes.fulfilled, (state, action) => { const chainID = action.payload.chainID; const result: VotesData = { @@ -276,11 +347,19 @@ export const govSlice = createSlice({ state.chains[chainID].votes = result; }) - .addCase(getVotes.rejected, () => {}); + .addCase(getVotes.rejected, (state, action) => { + const chainID = action.meta?.arg?.chainID; + const payload = action.payload as { message: string }; + state.chains[chainID].votes.status = TxStatus.REJECTED; + state.chains[chainID].votes.errMsg = payload.message || ''; + }); // tally builder - .addCase(getProposalTally.pending, () => {}) + .addCase(getProposalTally.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].tally.status = TxStatus.PENDING; + }) .addCase(getProposalTally.fulfilled, (state, action) => { const chainID = action.payload.chainID; const result = { @@ -293,7 +372,36 @@ export const govSlice = createSlice({ action.payload?.data.tally; state.chains[chainID].tally = result; }) - .addCase(getProposalTally.rejected, () => {}); + .addCase(getProposalTally.rejected, (state, action) => { + const chainID = action.meta?.arg?.chainID; + const payload = action.payload as { message: string }; + state.chains[chainID].tally.status = TxStatus.REJECTED; + state.chains[chainID].tally.errMsg = payload.message || ''; + }); + + //deposit params + builder + .addCase(getDepositParams.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].depositParams.status = TxStatus.PENDING; + }) + .addCase(getDepositParams.fulfilled, (state, action) => { + const chainID = action.payload?.chainID || ''; + if (chainID.length) { + const result = { + status: TxStatus.IDLE, + errMsg: '', + params: action.payload?.data?.deposit_params, + }; + state.chains[chainID].depositParams = result; + } + }) + .addCase(getDepositParams.rejected, (state, action) => { + const chainID = action.meta?.arg?.chainID; + const payload = action.payload as { message: string }; + state.chains[chainID].depositParams.status = TxStatus.REJECTED; + state.chains[chainID].depositParams.errMsg = payload.message || ''; + }); }, }); diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts index 5b84e5646..aa539fae1 100644 --- a/frontend/src/types/gov.d.ts +++ b/frontend/src/types/gov.d.ts @@ -88,6 +88,30 @@ interface ProposalTallyData { proposalTally: ProposalTally; } +interface DepositParams { + min_deposit: { + denom: string; + amount: string; + }[]; + max_deposit_period: string; +} + +interface TallyParams { + quorum: string; + threshold: string; + vote_threshold: string; +} + +interface VotingParams { + voting_period: string; +} + +interface GovParamsResponse { + voting_params: VotingParams; + deposit_params: DepositParams; + tally_params: TallyParams; +} + interface GetProposalsInVotingInputs { baseURL: string; chainID: string; @@ -117,3 +141,8 @@ interface GetProposalTallyInputs { proposalId: number; chainID: string; } + +interface GetDepositParamsInputs { + baseURL: string; + chainID: string; +} \ No newline at end of file From 6c9340b2cecc73356173dde506d96439fabb5219 Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sat, 25 Nov 2023 20:59:59 +0530 Subject: [PATCH 06/10] wip(gov): add get proposal --- frontend/src/store/features/gov/govService.ts | 8 ++- frontend/src/store/features/gov/govSlice.ts | 69 +++++++++++++++++++ frontend/src/types/gov.d.ts | 10 ++- frontend/src/utils/constants.ts | 3 +- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/frontend/src/store/features/gov/govService.ts b/frontend/src/store/features/gov/govService.ts index 4be9bfd94..bab0c3d44 100644 --- a/frontend/src/store/features/gov/govService.ts +++ b/frontend/src/store/features/gov/govService.ts @@ -1,6 +1,10 @@ import Axios, { AxiosResponse } from 'axios'; import { convertPaginationToParams, cleanURL } from '../../../utils/util'; -import { GetProposalsInVotingResponse, ProposalVote } from '@/types/gov'; +import { + GetProposalsInVotingResponse, + GovProposal, + ProposalVote, +} from '@/types/gov'; const proposalsURL = '/cosmos/gov/v1beta1/proposals'; const proposalTallyURL = (id: number): string => @@ -56,7 +60,7 @@ const fetchVoterVote = ( const fetchProposal = ( baseURL: string, proposalId: number -): Promise => +): Promise> => Axios.get(`${cleanURL(baseURL)}${proposalsURL}/${proposalId}`); const fetchDepositParams = (baseURL: string): Promise => diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index 10252f642..b9742231c 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -17,7 +17,9 @@ import { GovPagination, DepositParams, GetDepositParamsInputs, + GetProposalInputs, } from '@/types/gov'; +import { PROPOSAL_STATUS_VOTING_PERIOD } from '@/utils/constants'; interface Chain { active: { @@ -48,6 +50,10 @@ interface Chains { interface GovState { chains: Chains; defaultState: Chain; + proposalInfo: { + status: TxStatus; + errMsg: string; + }; } const EMPTY_PAGINATION = { @@ -100,8 +106,29 @@ const initialState: GovState = { proposalTally: {}, }, }, + proposalInfo: { + status: TxStatus.INIT, + errMsg: '', + }, }; +export const getProposal = createAsyncThunk( + 'gov/proposal-info', + async (data: GetProposalInputs, { rejectWithValue }) => { + try { + const response = await govService.proposal(data.baseURL, data.proposalId); + return { + chainID: data.chainID, + data: response.data, + }; + } catch (error) { + if (error instanceof AxiosError) + return rejectWithValue({ message: error.message }); + return rejectWithValue({ message: ERR_UNKNOWN }); + } + } +); + export const getProposalsInDeposit = createAsyncThunk( 'gov/deposit-proposals', async (data: GetProposalsInDepositInputs, { rejectWithValue, dispatch }) => { @@ -402,6 +429,48 @@ export const govSlice = createSlice({ state.chains[chainID].depositParams.status = TxStatus.REJECTED; state.chains[chainID].depositParams.errMsg = payload.message || ''; }); + + // get one proposal + builder + .addCase(getProposal.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + if (!state.chains[chainID]) + state.chains[chainID] = cloneDeep(initialState.defaultState); + state.proposalInfo.status = TxStatus.PENDING; + }) + .addCase(getProposal.fulfilled, (state, action) => { + const chainID = action.meta.arg?.chainID || ''; + if ( + action.payload.data?.proposal.status === PROPOSAL_STATUS_VOTING_PERIOD + ) { + const currentProposalsState = state.chains[chainID].active.proposals; + if (!currentProposalsState.length) { + const result = { + status: TxStatus.IDLE, + errMsg: '', + proposals: [action.payload.data.proposal], + }; + state.chains[chainID].active = result; + } + } else { + const currentProposalsState = state.chains[chainID].deposit.proposals; + if (!currentProposalsState.length) { + const result = { + status: TxStatus.IDLE, + errMsg: '', + proposals: [action.payload.data.proposal], + }; + state.chains[chainID].deposit = result; + } + } + state.proposalInfo.status = TxStatus.IDLE; + state.proposalInfo.errMsg = ''; + }) + .addCase(getProposal.rejected, (state, action) => { + state.proposalInfo.status = TxStatus.REJECTED; + const payload = action.payload as { message: string }; + state.proposalInfo.errMsg = payload.message || ''; + }); }, }); diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts index aa539fae1..6e15ff403 100644 --- a/frontend/src/types/gov.d.ts +++ b/frontend/src/types/gov.d.ts @@ -35,7 +35,7 @@ interface GovPagination { } interface GetProposalsInVotingResponse { - proposals: ActiveProposal[]; + proposals: GovProposal[]; pagination: GovPagination; } @@ -145,4 +145,10 @@ interface GetProposalTallyInputs { interface GetDepositParamsInputs { baseURL: string; chainID: string; -} \ No newline at end of file +} + +interface GetProposalInputs { + baseURL: string; + proposalId: number; + chainID: string; +} diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 2554b4cc9..338458d3a 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -1 +1,2 @@ -export const GAS_FEE = 860000 \ No newline at end of file +export const GAS_FEE = 860000 +export const PROPOSAL_STATUS_VOTING_PERIOD = "PROPOSAL_STATUS_VOTING_PERIOD" \ No newline at end of file From e6048e9054f69c30de05620ee77aad513d6cbe31 Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sat, 25 Nov 2023 21:08:42 +0530 Subject: [PATCH 07/10] (gov): add constants --- frontend/src/store/features/gov/govSlice.ts | 7 +++++-- frontend/src/utils/constants.ts | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index b9742231c..5d863a02d 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -21,6 +21,9 @@ import { } from '@/types/gov'; import { PROPOSAL_STATUS_VOTING_PERIOD } from '@/utils/constants'; +const PROPSAL_STATUS_DEPOSIT = 1; +const PROPOSAL_STATUS_ACTIVE = 2; + interface Chain { active: { status: TxStatus; @@ -137,7 +140,7 @@ export const getProposalsInDeposit = createAsyncThunk( data.baseURL, data?.key, data?.limit, - 1 + PROPSAL_STATUS_DEPOSIT ); if (response?.data?.proposals?.length && data?.chainID?.length) { @@ -187,7 +190,7 @@ export const getProposalsInVoting = createAsyncThunk( data.baseURL, data.key, data.limit, - 2 + PROPOSAL_STATUS_ACTIVE ); if (response?.data?.proposals?.length) { diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 338458d3a..26d758f1c 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -1,2 +1,2 @@ -export const GAS_FEE = 860000 -export const PROPOSAL_STATUS_VOTING_PERIOD = "PROPOSAL_STATUS_VOTING_PERIOD" \ No newline at end of file +export const GAS_FEE = 860000; +export const PROPOSAL_STATUS_VOTING_PERIOD = 'PROPOSAL_STATUS_VOTING_PERIOD'; \ No newline at end of file From c9a34b46e182949014158dac3007ee74cfbbd516 Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Sun, 26 Nov 2023 01:57:36 +0530 Subject: [PATCH 08/10] feat(gov): Add txDeposit and txVote --- frontend/src/store/features/gov/govSlice.ts | 202 +++++++++++++++++++- frontend/src/txns/gov/deposit.ts | 24 +++ frontend/src/txns/gov/index.ts | 2 + frontend/src/txns/gov/vote.ts | 19 ++ frontend/src/types/gov.d.ts | 37 +++- 5 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 frontend/src/txns/gov/deposit.ts create mode 100644 frontend/src/txns/gov/index.ts create mode 100644 frontend/src/txns/gov/vote.ts diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index 5d863a02d..c47fa196c 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -1,6 +1,6 @@ 'use client'; -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import govService from './govService'; import { cloneDeep } from 'lodash'; import { AxiosError } from 'axios'; @@ -18,8 +18,13 @@ import { DepositParams, GetDepositParamsInputs, GetProposalInputs, + TxVoteInputs, + TxDepositInputs, } from '@/types/gov'; import { PROPOSAL_STATUS_VOTING_PERIOD } from '@/utils/constants'; +import { signAndBroadcast } from '@/utils/signing'; +import { setError, setTxHash } from '../common/commonSlice'; +import { GovDepositMsg, GovVoteMsg } from '@/txns/gov'; const PROPSAL_STATUS_DEPOSIT = 1; const PROPOSAL_STATUS_ACTIVE = 2; @@ -44,6 +49,10 @@ interface Chain { }; votes: VotesData; tally: ProposalTallyData; + tx: { + status: TxStatus; + txHash: string; + }; } interface Chains { @@ -57,6 +66,8 @@ interface GovState { status: TxStatus; errMsg: string; }; + activeProposalsLoading: number; + depositProposalsLoading: number; } const EMPTY_PAGINATION = { @@ -66,6 +77,8 @@ const EMPTY_PAGINATION = { const initialState: GovState = { chains: {}, + activeProposalsLoading: 0, + depositProposalsLoading: 0, defaultState: { active: { status: TxStatus.INIT, @@ -108,6 +121,10 @@ const initialState: GovState = { errMsg: '', proposalTally: {}, }, + tx: { + status: TxStatus.INIT, + txHash: '', + }, }, proposalInfo: { status: TxStatus.INIT, @@ -273,10 +290,148 @@ export const getProposalTally = createAsyncThunk( } ); +export const txVote = createAsyncThunk( + 'gov/tx-vote', + async ( + data: TxVoteInputs, + { rejectWithValue, fulfillWithValue, dispatch } + ) => { + try { + const msg = GovVoteMsg(data.proposalId, data.voter, data.option); + const result = await signAndBroadcast( + data.chainID, + data.aminoConfig, + data.prefix, + [msg], + 860000, + data?.justification || '', + `${data.feeAmount}${data.denom}`, + data.rest, + data.feegranter?.length > 0 ? data.feegranter : undefined + ); + if (result?.code === 0) { + dispatch( + setTxHash({ + hash: result?.transactionHash, + }) + ); + + return fulfillWithValue({ txHash: result?.transactionHash }); + } else { + dispatch( + setError({ + type: 'error', + message: result?.rawLog || '', + }) + ); + return rejectWithValue(result?.rawLog); + } + } catch (error) { + if (error instanceof AxiosError) { + dispatch( + setError({ + type: 'error', + message: error.message, + }) + ); + return rejectWithValue(error.response); + } + dispatch( + setError({ + type: 'error', + message: ERR_UNKNOWN, + }) + ); + return rejectWithValue(ERR_UNKNOWN); + } + } +); + +export const txDeposit = createAsyncThunk( + 'gov/tx-deposit', + async ( + data: TxDepositInputs, + { rejectWithValue, fulfillWithValue, dispatch } + ) => { + try { + const msg = GovDepositMsg( + data.proposalId, + data.depositer, + data.amount, + data.denom + ); + const result = await signAndBroadcast( + data.chainID, + data.aminoConfig, + data.prefix, + [msg], + 860000, + data?.justification || '', + `${data.feeAmount}${data.denom}`, + data.rest, + data.feegranter?.length > 0 ? data.feegranter : undefined + ); + if (result?.code === 0) { + dispatch( + setTxHash({ + hash: result?.transactionHash, + }) + ); + return fulfillWithValue({ txHash: result?.transactionHash }); + } else { + dispatch( + setError({ + type: 'error', + message: result?.rawLog || '', + }) + ); + return rejectWithValue(result?.rawLog); + } + } catch (error) { + if (error instanceof AxiosError) { + dispatch( + setError({ + type: 'error', + message: error.message, + }) + ); + return rejectWithValue(error.response); + } + dispatch( + setError({ + type: 'error', + message: ERR_UNKNOWN, + }) + ); + return rejectWithValue(ERR_UNKNOWN); + } + } +); + export const govSlice = createSlice({ name: 'gov', initialState, - reducers: {}, + reducers: { + resetTx: (state, action: PayloadAction<{ chainID: string }>) => { + const chainID = action.payload.chainID; + state.chains[chainID].tx = { + status: TxStatus.INIT, + txHash: '', + }; + }, + resetState: (state, action: PayloadAction<{ chainID: string }>) => { + const { chainID } = action.payload; + state.chains[chainID] = cloneDeep(initialState.defaultState); + }, + resetDefaultState: (state, action: PayloadAction) => { + const chainsMap: Chains = {}; + const chains = action.payload; + chains.map((chainID) => { + chainsMap[chainID] = cloneDeep(initialState.defaultState); + }); + state.chains = chainsMap; + }, + }, extraReducers: (builder) => { // active proposals builder @@ -292,6 +447,7 @@ export const govSlice = createSlice({ pagination: EMPTY_PAGINATION, }; state.chains[chainID].active = result; + state.activeProposalsLoading++; }) .addCase(getProposalsInVoting.fulfilled, (state, action) => { const chainID = action.payload?.chainID || ''; @@ -303,6 +459,7 @@ export const govSlice = createSlice({ pagination: action.payload?.data?.pagination, }; state.chains[chainID].active = result; + state.activeProposalsLoading--; } }) .addCase(getProposalsInVoting.rejected, (state, action) => { @@ -316,6 +473,7 @@ export const govSlice = createSlice({ pagination: EMPTY_PAGINATION, }; state.chains[chainID].active = result; + state.activeProposalsLoading--; }); //proposals in deposit period @@ -332,6 +490,7 @@ export const govSlice = createSlice({ pagination: EMPTY_PAGINATION, }; state.chains[chainID].deposit = result; + state.depositProposalsLoading++; }) .addCase(getProposalsInDeposit.fulfilled, (state, action) => { const chainID = action.payload?.chainID || ''; @@ -343,6 +502,7 @@ export const govSlice = createSlice({ pagination: action.payload?.data?.pagination, }; state.chains[chainID].deposit = result; + state.depositProposalsLoading--; } }) .addCase(getProposalsInDeposit.rejected, (state, action) => { @@ -356,6 +516,7 @@ export const govSlice = createSlice({ pagination: EMPTY_PAGINATION, }; state.chains[chainID].deposit = result; + state.depositProposalsLoading--; }); // votes @@ -474,8 +635,43 @@ export const govSlice = createSlice({ const payload = action.payload as { message: string }; state.proposalInfo.errMsg = payload.message || ''; }); + + // tx-vote + builder + .addCase(txVote.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].tx.status = TxStatus.PENDING; + state.chains[chainID].tx.txHash = ''; + }) + .addCase(txVote.fulfilled, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].tx.status = TxStatus.IDLE; + state.chains[chainID].tx.txHash = action.payload.txHash; + }) + .addCase(txVote.rejected, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].tx.status = TxStatus.REJECTED; + state.chains[chainID].tx.txHash = ''; + }); + + builder + .addCase(txDeposit.pending, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].tx.status = TxStatus.PENDING; + state.chains[chainID].tx.txHash = ''; + }) + .addCase(txDeposit.fulfilled, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].tx.status = TxStatus.IDLE; + state.chains[chainID].tx.txHash = action.payload.txHash; + }) + .addCase(txDeposit.rejected, (state, action) => { + const chainID = action.meta?.arg?.chainID; + state.chains[chainID].tx.status = TxStatus.REJECTED; + state.chains[chainID].tx.txHash = ''; + }); }, }); -export const {} = govSlice.actions; +export const { resetTx, resetState, resetDefaultState } = govSlice.actions; export default govSlice.reducer; diff --git a/frontend/src/txns/gov/deposit.ts b/frontend/src/txns/gov/deposit.ts new file mode 100644 index 000000000..b10b10d34 --- /dev/null +++ b/frontend/src/txns/gov/deposit.ts @@ -0,0 +1,24 @@ +import { MsgDeposit } from 'cosmjs-types/cosmos/gov/v1beta1/tx'; + +const msgDeposit = '/cosmos.gov.v1beta1.MsgDeposit'; + +export function GovDepositMsg( + proposalId: number, + depositer: string, + amount: number, + denom: string +): Msg { + return { + typeUrl: msgDeposit, + value: MsgDeposit.fromPartial({ + depositor: depositer, + proposalId: BigInt(proposalId), + amount: [ + { + denom: denom, + amount: String(amount), + }, + ], + }), + }; +} diff --git a/frontend/src/txns/gov/index.ts b/frontend/src/txns/gov/index.ts new file mode 100644 index 000000000..347312c7b --- /dev/null +++ b/frontend/src/txns/gov/index.ts @@ -0,0 +1,2 @@ +export { GovVoteMsg } from './vote'; +export { GovDepositMsg } from './deposit'; diff --git a/frontend/src/txns/gov/vote.ts b/frontend/src/txns/gov/vote.ts new file mode 100644 index 000000000..ef35a50a7 --- /dev/null +++ b/frontend/src/txns/gov/vote.ts @@ -0,0 +1,19 @@ +import { VoteOption } from 'cosmjs-types/cosmos/gov/v1beta1/gov'; +import { MsgVote } from 'cosmjs-types/cosmos/gov/v1beta1/tx'; + +const msgVote = '/cosmos.gov.v1beta1.MsgVote'; + +export function GovVoteMsg( + proposalId: number, + voter: string, + option: VoteOption +): Msg { + return { + typeUrl: msgVote, + value: MsgVote.fromPartial({ + voter: voter, + option: option, + proposalId: BigInt(proposalId), + }), + }; +} diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts index 6e15ff403..aee686822 100644 --- a/frontend/src/types/gov.d.ts +++ b/frontend/src/types/gov.d.ts @@ -2,15 +2,10 @@ import { TxStatus } from './enums'; interface GovProposal { proposal_id: string; - content: { + content?: { '@type': string; title: string; description: string; - changes?: { - subspace: string; - key: string; - value: string; - }[]; }; status: string; final_tally_result: { @@ -152,3 +147,33 @@ interface GetProposalInputs { proposalId: number; chainID: string; } + +interface TxVoteInputs { + voter: string; + proposalId: number; + option: number; + denom: string; + chainID: string; + rpc: string; + rest: string; + aminoConfig: AminoConfig; + prefix: string; + feeAmount: number; + feegranter: string; + justification?: string; +} + +interface TxDepositInputs { + depositer: string; + proposalId: number; + amount: number; + denom: string; + chainID: string; + rpc: string; + rest: string; + aminoConfig: AminoConfig; + prefix: string; + feeAmount: number; + feegranter: string; + justification?: string; +} From 16b2098f7235e30426cecc1707ee54317d5ef7eb Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Mon, 27 Nov 2023 12:02:31 +0530 Subject: [PATCH 09/10] chore(gov): review changes --- frontend/src/store/features/gov/govSlice.ts | 5 ++--- frontend/src/types/gov.d.ts | 5 ----- frontend/src/types/types.d.ts | 5 +++++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index c47fa196c..e1cc2334a 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -14,7 +14,6 @@ import { GetVotesInputs, ProposalTallyData, VotesData, - GovPagination, DepositParams, GetDepositParamsInputs, GetProposalInputs, @@ -34,13 +33,13 @@ interface Chain { status: TxStatus; errMsg: string; proposals: GovProposal[]; - pagination?: GovPagination; + pagination?: Pagination; }; deposit: { status: TxStatus; errMsg: string; proposals: GovProposal[]; - pagination?: GovPagination; + pagination?: Pagination; }; depositParams: { status: TxStatus; diff --git a/frontend/src/types/gov.d.ts b/frontend/src/types/gov.d.ts index aee686822..398b5a906 100644 --- a/frontend/src/types/gov.d.ts +++ b/frontend/src/types/gov.d.ts @@ -24,11 +24,6 @@ interface GovProposal { voting_end_time: string; } -interface GovPagination { - next_key?: string; - total: string; -} - interface GetProposalsInVotingResponse { proposals: GovProposal[]; pagination: GovPagination; diff --git a/frontend/src/types/types.d.ts b/frontend/src/types/types.d.ts index 6498d8b6f..b12c44159 100644 --- a/frontend/src/types/types.d.ts +++ b/frontend/src/types/types.d.ts @@ -8,3 +8,8 @@ type KeyLimitPagination = { key?: string; limit?: number; } + +interface Pagination { + next_key?: string; + total: string; +} \ No newline at end of file From aa1fbdd7fdce72284df7be78efe5f601476288fd Mon Sep 17 00:00:00 2001 From: hemanthghs Date: Mon, 27 Nov 2023 14:26:18 +0530 Subject: [PATCH 10/10] refactor(gov): review changes --- frontend/src/store/features/gov/govSlice.ts | 68 +++++++++------------ 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/frontend/src/store/features/gov/govSlice.ts b/frontend/src/store/features/gov/govSlice.ts index e1cc2334a..f0a0d9f70 100644 --- a/frontend/src/store/features/gov/govSlice.ts +++ b/frontend/src/store/features/gov/govSlice.ts @@ -20,7 +20,7 @@ import { TxVoteInputs, TxDepositInputs, } from '@/types/gov'; -import { PROPOSAL_STATUS_VOTING_PERIOD } from '@/utils/constants'; +import { GAS_FEE, PROPOSAL_STATUS_VOTING_PERIOD } from '@/utils/constants'; import { signAndBroadcast } from '@/utils/signing'; import { setError, setTxHash } from '../common/commonSlice'; import { GovDepositMsg, GovVoteMsg } from '@/txns/gov'; @@ -209,31 +209,30 @@ export const getProposalsInVoting = createAsyncThunk( PROPOSAL_STATUS_ACTIVE ); - if (response?.data?.proposals?.length) { - const proposals = response?.data?.proposals; - for (let i = 0; i < proposals.length; i++) { - dispatch( - getProposalTally({ - baseURL: data.baseURL, - proposalId: Number(proposals[i].proposal_id), - chainID: data.chainID, - }) - ); - - dispatch( - getVotes({ - baseURL: data.baseURL, - proposalId: Number(proposals[i].proposal_id), - voter: data.voter, - chainID: data.chainID, - }) - ); - } - } + const { data: responseData } = response || {}; + const proposals = responseData?.proposals || []; + proposals.forEach((proposal) => { + const proposalId = Number(proposal.proposal_id); + dispatch( + getProposalTally({ + baseURL: data?.baseURL, + proposalId, + chainID: data?.chainID, + }) + ); + dispatch( + getVotes({ + baseURL: data?.baseURL, + proposalId, + voter: data?.voter, + chainID: data?.chainID, + }) + ); + }); return { chainID: data.chainID, - data: response.data, + data: responseData, }; } catch (error) { if (error instanceof AxiosError) @@ -302,7 +301,7 @@ export const txVote = createAsyncThunk( data.aminoConfig, data.prefix, [msg], - 860000, + GAS_FEE, data?.justification || '', `${data.feeAmount}${data.denom}`, data.rest, @@ -370,21 +369,14 @@ export const txDeposit = createAsyncThunk( data.rest, data.feegranter?.length > 0 ? data.feegranter : undefined ); - if (result?.code === 0) { - dispatch( - setTxHash({ - hash: result?.transactionHash, - }) - ); - return fulfillWithValue({ txHash: result?.transactionHash }); + const { code, transactionHash, rawLog } = result || {}; + + if (code === 0) { + dispatch(setTxHash({ hash: transactionHash })); + return fulfillWithValue({ txHash: transactionHash }); } else { - dispatch( - setError({ - type: 'error', - message: result?.rawLog || '', - }) - ); - return rejectWithValue(result?.rawLog); + dispatch(setError({ type: 'error', message: rawLog || '' })); + return rejectWithValue(rawLog); } } catch (error) { if (error instanceof AxiosError) {