diff --git a/CHANGELOG.md b/CHANGELOG.md index b1361881b..48ccfa59e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#757](https://github.com/alleslabs/celatone-frontend/pull/757) api v1 - proposal data - [#731](https://github.com/alleslabs/celatone-frontend/pull/731) Add proposal detail page structure - [#749](https://github.com/alleslabs/celatone-frontend/pull/749) Add multi-type proposals - [#753](https://github.com/alleslabs/celatone-frontend/pull/753) api v1 - proposal type filter diff --git a/src/lib/app-provider/env.ts b/src/lib/app-provider/env.ts index faf280141..d74ca2b0c 100644 --- a/src/lib/app-provider/env.ts +++ b/src/lib/app-provider/env.ts @@ -51,6 +51,7 @@ export enum CELATONE_QUERY_KEYS { // FAUCET FAUCET_INFO = "CELATONE_QUERY_FAUCET_INFO", // X/GOV + PROPOSAL_DATA = "CELATONE_QUERY_PROPOSAL_DATA", RELATED_PROPOSALS_BY_CONTRACT_ADDRESS = "CELATONE_QUERY_RELATED_PROPOSALS_BY_CONTRACT_ADDRESS", PROPOSALS_BY_MODULE_ID = "CELATONE_QUERY_PROPOSALS_BY_MODULE_ID", PROPOSALS_COUNT_BY_MODULE_ID = "CELATONE_QUERY_PROPOSALS_COUNT_BY_MODULE_ID", diff --git a/src/lib/components/table/proposals/ProposalsTable.tsx b/src/lib/components/table/proposals/ProposalsTable.tsx index 65ad75314..1a9531c3c 100644 --- a/src/lib/components/table/proposals/ProposalsTable.tsx +++ b/src/lib/components/table/proposals/ProposalsTable.tsx @@ -30,10 +30,7 @@ export const ProposalsTable = ({ return isMobile ? ( {proposals.map((proposal) => ( - + ))} ) : ( @@ -44,7 +41,7 @@ export const ProposalsTable = ({ /> {proposals.map((proposal) => ( @@ -101,7 +101,7 @@ export const ProposalsTableMobileCard = ({ getNavigationUrl({ type: "proposal_id", explorerConfig: explorerLink, - value: proposal.proposalId.toString(), + value: proposal.id.toString(), lcdEndpoint, }) ); diff --git a/src/lib/components/table/proposals/ProposalsTableRow.tsx b/src/lib/components/table/proposals/ProposalsTableRow.tsx index 925ac4811..a0f30c1d1 100644 --- a/src/lib/components/table/proposals/ProposalsTableRow.tsx +++ b/src/lib/components/table/proposals/ProposalsTableRow.tsx @@ -62,7 +62,7 @@ export const ProposalsTableRow = ({ getNavigationUrl({ type: "proposal_id", explorerConfig: explorerLink, - value: proposal.proposalId.toString(), + value: proposal.id.toString(), lcdEndpoint, }) ); @@ -74,7 +74,7 @@ export const ProposalsTableRow = ({ diff --git a/src/lib/services/proposal.ts b/src/lib/services/proposal.ts index 382bcee8e..74f65099a 100644 --- a/src/lib/services/proposal.ts +++ b/src/lib/services/proposal.ts @@ -2,7 +2,13 @@ import type { Coin } from "@cosmjs/stargate"; import axios from "axios"; import { z } from "zod"; -import { zUtcDate, zProposalType, zBechAddr, zProposalStatus } from "lib/types"; +import { + zBechAddr, + zCoin, + zProposalStatus, + zProposalType, + zUtcDate, +} from "lib/types"; import type { AccessConfigPermission, BechAddr, @@ -11,6 +17,7 @@ import type { Proposal, Option, BechAddr20, + ProposalData, ProposalStatus, ProposalType, } from "lib/types"; @@ -72,29 +79,18 @@ export const getProposalTypes = async (endpoint: string) => .get(`${endpoint}/types`) .then(({ data }) => zProposalType.array().parse(data)); -const zProposalsResponseItem = z - .object({ - deposit_end_time: zUtcDate, - id: z.number().nonnegative(), - is_expedited: z.boolean(), - proposer: zBechAddr, - resolved_height: z.number().nullable(), - status: zProposalStatus, - title: z.string(), - types: zProposalType.array(), - voting_end_time: zUtcDate.nullable(), - }) - .transform((val) => ({ - depositEndTime: val.deposit_end_time, - proposalId: val.id, - isExpedited: val.is_expedited, - proposer: val.proposer, - resolvedHeight: val.resolved_height, - status: val.status, - title: val.title, - types: val.types, - votingEndTime: val.voting_end_time, - })); +const zProposal = z.object({ + deposit_end_time: zUtcDate, + id: z.number().nonnegative(), + is_expedited: z.boolean(), + proposer: zBechAddr, + resolved_height: z.number().nullable(), + status: zProposalStatus, + title: z.string(), + types: zProposalType.array(), + voting_end_time: zUtcDate.nullable(), +}); +const zProposalsResponseItem = zProposal.transform(snakeToCamel); const zProposalsResponse = z.object({ items: z.array(zProposalsResponseItem), @@ -164,3 +160,42 @@ export const getRelatedProposalsByContractAddress = async ( } ) .then(({ data }) => zRelatedProposalsResponse.parse(data)); + +const zProposalDataResponse = z.object({ + info: zProposal + .extend({ + created_height: z.number().nullable(), + created_timestamp: zUtcDate.nullable(), + created_tx_hash: z.string().nullable(), + description: z.string(), + messages: z.unknown().array().nullable(), + metadata: z.string(), + proposal_deposits: z + .object({ + amount: zCoin.array(), + depositor: zBechAddr, + timestamp: zUtcDate, + tx_hash: z.string(), + }) + .array(), + resolved_timestamp: zUtcDate.nullable(), + submit_time: zUtcDate, + total_deposit: zCoin.array(), + version: z.string(), + voting_time: zUtcDate.nullable(), + }) + .transform(({ messages, ...val }) => ({ + ...snakeToCamel(val), + messages, + })) + .nullable(), +}); +export type ProposalDataResponse = z.infer; + +export const getProposalData = async ( + endpoint: string, + id: number +): Promise => + axios + .get(`${endpoint}/${encodeURIComponent(id)}/info`) + .then(({ data }) => zProposalDataResponse.parse(data)); diff --git a/src/lib/services/proposalService.ts b/src/lib/services/proposalService.ts index 0ce729a43..db01582ea 100644 --- a/src/lib/services/proposalService.ts +++ b/src/lib/services/proposalService.ts @@ -38,6 +38,7 @@ import { useAssetInfos } from "./assetService"; import { useMovePoolInfos } from "./move"; import type { DepositParamsInternal, + ProposalDataResponse, ProposalsResponse, RelatedProposalsResponse, UploadAccess, @@ -51,6 +52,7 @@ import { getRelatedProposalsByContractAddress, getProposals, getProposalTypes, + getProposalData, } from "./proposal"; export const useProposals = ( @@ -163,7 +165,7 @@ export const useRelatedProposalsByModuleIdPagination = ( }) .then(({ module_proposals }) => module_proposals.map((proposal) => ({ - proposalId: proposal.proposal_id, + id: proposal.proposal_id, title: proposal.proposal.title, status: proposal.proposal.status as ProposalStatus, votingEndTime: parseDate(proposal.proposal.voting_end_time), @@ -222,6 +224,16 @@ export const useRelatedProposalsCountByModuleId = ( ); }; +export const useProposalData = (id: number) => { + const endpoint = useBaseApiRoute("proposals"); + + return useQuery( + [CELATONE_QUERY_KEYS.PROPOSAL_DATA, endpoint, id], + async () => getProposalData(endpoint, id), + { retry: 1, keepPreviousData: true } + ); +}; + export interface MinDeposit { amount: U>; denom: string; diff --git a/src/lib/types/asset.ts b/src/lib/types/asset.ts index 73a920854..4fafdb768 100644 --- a/src/lib/types/asset.ts +++ b/src/lib/types/asset.ts @@ -3,6 +3,11 @@ import { z } from "zod"; import type { PoolInfo, Option, Token, U, USD } from "lib/types"; +export const zCoin = z.object({ + denom: z.string(), + amount: z.string(), +}); + export const zAssetInfo = z.object({ coingecko: z.string(), description: z.string(), diff --git a/src/lib/types/proposal.ts b/src/lib/types/proposal.ts index bdf7f66de..8efbb6b56 100644 --- a/src/lib/types/proposal.ts +++ b/src/lib/types/proposal.ts @@ -1,3 +1,4 @@ +import type { Coin } from "@cosmjs/amino"; import { z } from "zod"; import type { BechAddr, Nullable, Option } from "lib/types"; @@ -57,7 +58,7 @@ export const zProposalType = z.union([ export type ProposalType = z.infer; export interface Proposal { - proposalId: number; + id: number; title: string; status: ProposalStatus; votingEndTime: Nullable; @@ -67,3 +68,25 @@ export interface Proposal { proposer: Option; isExpedited: boolean; } + +export interface ProposalDeposit { + amount: Coin[]; + depositor: BechAddr; + timestamp: Date; + txHash: string; +} + +export interface ProposalData extends Proposal { + createdHeight: Nullable; + createdTimestamp: Nullable; + createdTxHash: Nullable; + description: string; + messages: Nullable; + metadata: string; + proposalDeposits: ProposalDeposit[]; + resolvedTimestamp: Nullable; + submitTime: Date; + totalDeposit: Coin[]; + version: string; + votingTime: Nullable; +}