From 9e9d7ec3eaf2bd97d8e814c3c7b2db6f1eb938ef Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Thu, 3 Aug 2023 15:10:52 +0200 Subject: [PATCH] feat: move dialog to separate file --- apps/namada-interface/package.json | 1 + .../src/App/App.components.ts | 2 + .../App/Governance/Governance.components.ts | 13 +- .../src/App/Governance/Governance.tsx | 274 ++++-------------- .../src/App/Governance/ProposalDetails.tsx | 189 ++++++++++++ .../src/slices/StakingAndGovernance/README.md | 8 - .../slices/StakingAndGovernance/fakeData.ts | 145 --------- apps/namada-interface/src/slices/index.ts | 1 + apps/namada-interface/src/slices/proposals.ts | 95 ++++++ apps/namada-interface/src/store/mocks.ts | 31 +- apps/namada-interface/src/store/store.ts | 2 + packages/shared/lib/src/query.rs | 22 +- packages/shared/package.json | 2 + packages/shared/src/index.ts | 28 +- packages/shared/src/types.ts | 34 +++ yarn.lock | 10 + 16 files changed, 458 insertions(+), 399 deletions(-) create mode 100644 apps/namada-interface/src/App/Governance/ProposalDetails.tsx delete mode 100644 apps/namada-interface/src/slices/StakingAndGovernance/README.md delete mode 100644 apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts create mode 100644 apps/namada-interface/src/slices/proposals.ts create mode 100644 packages/shared/src/types.ts diff --git a/apps/namada-interface/package.json b/apps/namada-interface/package.json index db62a5da4..38bd03c2a 100644 --- a/apps/namada-interface/package.json +++ b/apps/namada-interface/package.json @@ -17,6 +17,7 @@ "crypto-browserify": "^3.12.0", "dompurify": "^3.0.2", "framer-motion": "^6.2.8", + "io-ts": "^2.2.20", "next-qrcode": "^2.0.0", "path": "^0.12.7", "react": "^17.0.2", diff --git a/apps/namada-interface/src/App/App.components.ts b/apps/namada-interface/src/App/App.components.ts index cf749fc90..e3ca987d5 100644 --- a/apps/namada-interface/src/App/App.components.ts +++ b/apps/namada-interface/src/App/App.components.ts @@ -70,6 +70,8 @@ export const AppContainer = styled.div` export const AppLoader = styled.div` position: fixed; + top: 0; + left: 0; width: 100vw; height: 100vh; z-index: 10; diff --git a/apps/namada-interface/src/App/Governance/Governance.components.ts b/apps/namada-interface/src/App/Governance/Governance.components.ts index 199ff3754..3cb97460c 100644 --- a/apps/namada-interface/src/App/Governance/Governance.components.ts +++ b/apps/namada-interface/src/App/Governance/Governance.components.ts @@ -87,9 +87,12 @@ export const ProposalCardId = styled.span` margin-right: 4px; `; -export const ProposalCardVotes = styled.div<{ yes: number; no: number }>` - width: 32px; - height: 32px; +export const ProposalCardVotesContainer = styled.div<{ + yes: number; + no: number; +}>` + width: 40px; + height: 40px; background-image: conic-gradient( ${(props) => props.theme.colors.primary.main} 0, ${(props) => props.theme.colors.primary.main} ${(p) => p.yes}%, @@ -104,7 +107,7 @@ export const ProposalCardInfoContainer = styled.div` height: 40px; gap: 4px; - & ${ProposalCardVotes} { + & ${ProposalCardVotesContainer} { flex-shrink: 0; align-self: center; } @@ -114,7 +117,7 @@ export const ProposalCardInfoContainer = styled.div` } `; -export const ProposalDetails = styled.dialog` +export const ProposalDetailsContainer = styled.dialog` display: flex; align-items: center; justify-content: center; diff --git a/apps/namada-interface/src/App/Governance/Governance.tsx b/apps/namada-interface/src/App/Governance/Governance.tsx index 4f9d35ae9..477ce2450 100644 --- a/apps/namada-interface/src/App/Governance/Governance.tsx +++ b/apps/namada-interface/src/App/Governance/Governance.tsx @@ -6,13 +6,7 @@ import { ProposalCardStatusContainer, ProposalCardStatusInfo, ProposalCardText, - ProposalCardVoteButton, - ProposalCardVotes, - ProposalDetails, - ProposalDetailsAddresses, - ProposalDetailsAddressesHeader, - ProposalDetailsButtons, - ProposalDetailsContent, + ProposalCardVotesContainer, ProposalsContainer, } from "./Governance.components"; import { chains } from "@namada/chains"; @@ -21,17 +15,40 @@ import { useAppSelector } from "store"; import { useCallback, useEffect, useState } from "react"; import { Query } from "@namada/shared"; import BigNumber from "bignumber.js"; -import { getIntegration } from "@namada/hooks"; -import { AccountType, Signer, Tokens } from "@namada/types"; -import { Option, shortenAddress } from "@namada/utils"; -import { Select } from "@namada/components"; -import { AccountsState } from "slices/accounts"; +import { Option } from "@namada/utils"; +import { ProposalDetails } from "./ProposalDetails"; + +export type Proposal = { + id: string; + proposalType: string; + author: string; + startEpoch: bigint; + endEpoch: bigint; + graceEpoch: bigint; + content: Content; + status: string; + yesVotes?: string; + totalVotingPower?: string; + result?: string; +}; + +export type Content = { + abstract: string; + authors: string; + created: string; + details: string; + discussionsTo: string; + license: string; + motivation: string; + requires: string; + title: string; +}; const getStatus = (status: string, result?: string): string => { return result || status; }; -const ProposalCardVotes2 = ({ +const ProposalCardVotes = ({ yes, total, }: { @@ -45,7 +62,7 @@ const ProposalCardVotes2 = ({ const noPercentage = 100 - yesPercentage; return ( - { const { chainId } = useAppSelector((state) => state.settings); - const { derived } = useAppSelector((state) => state.accounts); - const addresses = Object.keys(derived[chainId]); const [proposals, setProposals] = useState([]); const [activeProposal, setActiveProposal] = useState>( Option.none() ); - const [activeDelegator, setActiveDelegator] = useState>( - Option.none() - ); - const [delegators, setDelegators] = useState< - Option> - >(Option.none()); - const [activeProposalVotes, setActiveProposalVotes] = useState< - Map - >(new Map()); const { rpc } = chains[chainId]; const query = new Query(rpc); - const vote = useCallback( - async (vote: boolean) => { - const voteStr = vote ? "yay" : "nay"; - const integration = getIntegration(chainId); - const signer = integration.signer() as Signer; - - const address = Option.match( - activeDelegator, - (address) => address, - () => { - throw new Error("No address selected"); - } - ); - - const proposalId = Option.match( - activeProposal, - (proposal) => proposal.id, - () => { - throw new Error("No proposal selected"); - } - ); - - await signer.submitVoteProposal( - { - tx: { - token: Tokens.NAM.address || "", - feeAmount: new BigNumber(0), - gasLimit: new BigNumber(0), - chainId, - }, - signer: address, - vote: voteStr, - proposalId: BigInt(proposalId), - }, - - AccountType.Mnemonic - ); - }, - [activeDelegator, activeProposal] - ); - useEffect(() => { const fetchProposals = async (): Promise => { try { - const proposals = await query.query_proposals(); - const p = proposals.map((proposal: any) => ({ + const sdkProposals = await query.query_proposals(); + const proposals = sdkProposals.map((proposal) => ({ ...proposal, - start_epoch: BigInt(proposal.start_epoch), - end_epoch: BigInt(proposal.end_epoch), - grace_epoch: BigInt(proposal.grace_epoch), + proposalType: proposal.proposal_type, + startEpoch: BigInt(proposal.start_epoch), + endEpoch: BigInt(proposal.end_epoch), + graceEpoch: BigInt(proposal.grace_epoch), + content: { + ...proposal.content, + discussionsTo: proposal.content["discussions-to"], + }, + totalVotingPower: proposal.total_voting_power, })); - setProposals(p); + setProposals(proposals); } catch (e) { console.error(e); setProposals([]); @@ -134,27 +105,13 @@ export const Governance = (): JSX.Element => { fetchProposals(); }, []); - const onProposalClick = useCallback( - async (proposal: Proposal) => { - try { - setActiveProposal(Option.some(proposal)); - - const votes = await query.get_proposal_votes(BigInt(proposal.id)); - setActiveProposalVotes(new Map(votes)); + const onProposalClick = useCallback((proposal: Proposal) => { + setActiveProposal(Option.some(proposal)); + }, []); - const totalDelegations = await query.get_total_delegations( - addresses, - //TODO: start_epoch should be BigInt - proposal.start_epoch - BigInt(1) - ); - setDelegators(Option.some(totalDelegations)); - setActiveDelegator(Option.some(Object.keys(totalDelegations)[0])); - } catch (e) { - // TODO: handle rpc error - } - }, - [addresses] - ); + const onDetailsClose = useCallback(() => { + setActiveProposal(Option.none()); + }, []); return ( @@ -174,136 +131,21 @@ export const Governance = (): JSX.Element => { {"#" + proposal.id} {proposal.content.title}: {proposal.content.details} - {proposal.yes_votes && proposal.total_voting_power && ( - )} ))} - {activeProposal.some && activeDelegator.some && delegators.some && ( - setActiveProposal(Option.none())}> - e.stopPropagation()}> -

- #{activeProposal.value.id} {activeProposal.value.content.title} -

-

by: {activeProposal.value.content.authors}

-

Details:

-

{activeProposal.value.content.details}

-

Motivation:

-

{activeProposal.value.content.motivation}

-

License:

-

{activeProposal.value.content.license}

- - {activeProposal.value.status === "on-going" && - !activeProposal.value.result && ( - <> - - - Vote with address: - - ({ + value: a, + label: shortenAddress(a, 32, 32), + }))} + onChange={(e) => { + setActiveDelegator(Option.some(e.target.value)); + }} + /> + Power: {delegations[delegatorAddress]} + + + vote(true)} + disabled={activeProposalVotes.get(delegatorAddress) === true} + title={ + activeProposalVotes.get(delegatorAddress) + ? "You have already voted YAY" + : "" + } + > + Vote YAY + + vote(false)} + disabled={activeProposalVotes.get(delegatorAddress) === false} + title={ + !activeProposalVotes.get(delegatorAddress) + ? "You have already voted NAY" + : "" + } + className="inverse" + > + Vote NAY + + + + )} +
+ + ); + } else { + return ; + } +}; diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/README.md b/apps/namada-interface/src/slices/StakingAndGovernance/README.md deleted file mode 100644 index 6a72ba094..000000000 --- a/apps/namada-interface/src/slices/StakingAndGovernance/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Staking And Governance -This part of the state deals with the data that is under Staking & Governance in the UI. It allows the user to perform all the actions regarding Staking, Governance and Public Goods Funding. - -These are the UIs the state is mainly serving: -* **Staking** - [https://specs.anoma.net/main/architecture/clients/web-wallet.html#stakingoverview](https://specs.anoma.net/main/architecture/clients/web-wallet.html#stakingoverview). -* **Governance** - [https://specs.anoma.net/main/architecture/clients/web-wallet.html#governanceproposals](https://specs.anoma.net/main/architecture/clients/web-wallet.html#governanceproposals). -* **Public goods funding** - [https://specs.anoma.net/main/architecture/clients/web-wallet.html#publicgoodsfundingoverview](https://specs.anoma.net/main/architecture/clients/web-wallet.html#publicgoodsfundingoverview). - diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts b/apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts deleted file mode 100644 index f1d5f2774..000000000 --- a/apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts +++ /dev/null @@ -1,145 +0,0 @@ -import BigNumber from "bignumber.js"; - -import { Validator, StakingPosition, MyBalanceEntry } from "./types"; -export const myBalancesData: MyBalanceEntry[] = [ - { - uuid: "1", - key: "Total Balance", - baseCurrency: "NAM 33.00", - fiatCurrency: "EUR 33.00", - }, - { - uuid: "2", - key: "Total Bonded", - baseCurrency: "NAM 10.00", - fiatCurrency: "EUR 10.00", - }, - { - uuid: "3", - key: "Pending Rewards", - baseCurrency: "NAM 23.00", - fiatCurrency: "EUR 23.00", - }, - { - uuid: "4", - key: "Available for Bonding", - baseCurrency: "NAM 10.00", - fiatCurrency: "EUR 10.00", - }, -]; - -export const myStakingData: StakingPosition[] = [ - { - uuid: "1", - bonded: true, - stakedAmount: new BigNumber(10_000_000), - owner: "some-owner", - totalRewards: "0.55", - validatorId: "polychain-capital", - }, - { - uuid: "2", - bonded: true, - stakedAmount: new BigNumber(3_000_000), - owner: "some-owner", - totalRewards: "0.15", - validatorId: "coinbase-custody", - }, - { - uuid: "3", - bonded: true, - stakedAmount: new BigNumber(20_000_000), - owner: "some-owner", - totalRewards: "1.05", - validatorId: "kraken", - }, -]; - -export const allValidatorsData: Validator[] = [ - { - uuid: "polychain-capital", - name: "Polychain capital", - homepageUrl: "https://polychain.capital", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.22), - description: - "Polychain is an investment firm committed to exceptional returns for investors through actively managed portfolios of blockchain assets.", - }, - { - uuid: "figment", - name: "Figment", - homepageUrl: "https://figment.io", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: - "Makers of Hubble and Canada’s largest Cosmos validator, Figment is the easiest and most secure way to stake your Atoms.", - }, - { - uuid: "p2p", - name: "P2P", - homepageUrl: "https://p2p.org", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: - "One of the winners of Cosmos Game of Stakes. We provide a simple, secure and intelligent staking service to help you generate rewards on your blockchain assets across 9+ networks within a single interface. Let’s stake together - p2p.org.", - }, - { - uuid: "coinbase-custody", - name: "Coinbase Custody", - homepageUrl: "https://custody.coinbase.com", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: "Coinbase Custody Cosmos Validator", - }, - { - uuid: "chorus-one", - name: "Chorus One", - homepageUrl: "https://chorus.one", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: - "Secure Cosmos and shape its future by delegating to Chorus One, a highly secure and stable validator. By delegating, you agree to the terms of service at: https://chorus.one/cosmos/tos", - }, - { - uuid: "binance-staking", - name: "Binance Staking", - homepageUrl: "https://binance.com", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: "Exchange the world", - }, - { - uuid: "dokiacapital", - name: "DokiaCapital", - homepageUrl: "https://staking.dokia.cloud", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: - "Downtime is not an option for Dokia Capital. We operate an enterprise-grade infrastructure that is robust and secure.", - }, - { - uuid: "kraken", - name: "Kraken", - homepageUrl: "https://kraken.com", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: "Kraken Exchange validator", - }, - { - uuid: "zero-knowledge-validator-(ZKV)", - name: "Zero Knowledge Validator (ZKV)", - homepageUrl: "https://zkvalidator.com", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: - "Zero Knowledge Validator: Stake & Support ZKP Research & Privacy Tech", - }, - { - uuid: "paradigm", - name: "Paradigm", - homepageUrl: "https://www.paradigm.xyz", - votingPower: new BigNumber(100_000), - commission: new BigNumber(0.20), - description: "", - }, -]; diff --git a/apps/namada-interface/src/slices/index.ts b/apps/namada-interface/src/slices/index.ts index 06ca80dbe..f2006f30d 100644 --- a/apps/namada-interface/src/slices/index.ts +++ b/apps/namada-interface/src/slices/index.ts @@ -3,5 +3,6 @@ export { default as transfersReducer } from "./transfers"; export { default as channelsReducer } from "./channels"; export { default as settingsReducer } from "./settings"; export { default as coinsReducer } from "./coins"; +export { default as proposalsReducer } from "./proposals"; export { notificationsReducer } from "./notifications"; export { stakingAndGovernanceReducers } from "./StakingAndGovernance"; diff --git a/apps/namada-interface/src/slices/proposals.ts b/apps/namada-interface/src/slices/proposals.ts new file mode 100644 index 000000000..aabc46bf5 --- /dev/null +++ b/apps/namada-interface/src/slices/proposals.ts @@ -0,0 +1,95 @@ +import { chains } from "@namada/chains"; +import { Query } from "@namada/shared"; +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import BigNumber from "bignumber.js"; +import { RootState } from "store"; + +//TODO: change case in those types +export type Proposal = { + id: string; + proposalType: string; + author: string; + startEpoch: bigint; + endEpoch: bigint; + graceEpoch: bigint; + content: Content; + status: string; + yesVotes?: string; + totalVotingPower?: string; + result?: string; +}; + +export type Content = { + abstract: string; + authors: string; + created: string; + details: string; + discussionsTo: string; + license: string; + motivation: string; + requires: string; + title: string; +}; + +export type ProposalsState = { + proposals: Proposal[]; + active?: { + proposalId: string; + delegations: Record; + }; +}; + +const PROPOSALS_ACTIONS_BASE = "proposals"; +const INITIAL_STATE: ProposalsState = { + proposals: [], +}; + +enum ProposalsActions { + FetchProposals = "fetchProposals", +} + +export const fetchProposals = createAsyncThunk< + Proposal[], + void, + { state: RootState } +>( + `${PROPOSALS_ACTIONS_BASE}/${ProposalsActions.FetchProposals}`, + async (_, thunkApi) => { + const state = thunkApi.getState(); + const chainId = state.settings.chainId; + const { rpc } = chains[chainId]; + const query = new Query(rpc); + let proposals: Proposal[] = []; + + try { + const sdkProposals = await query.query_proposals(); + proposals = sdkProposals.map((p) => ({ + ...p, + proposalType: p.proposal_type, + startEpoch: BigInt(p.start_epoch), + endEpoch: BigInt(p.end_epoch), + graceEpoch: BigInt(p.grace_epoch), + content: { ...p.content, discussionsTo: p.content["discussions-to"] }, + })); + } catch (e) { + console.error(e); + } + + return proposals; + } +); + +const proposalsSlice = createSlice({ + name: PROPOSALS_ACTIONS_BASE, + initialState: INITIAL_STATE, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchProposals.fulfilled, (state, action) => { + state.proposals = action.payload; + }); + }, +}); + +const { reducer } = proposalsSlice; + +export default reducer; diff --git a/apps/namada-interface/src/store/mocks.ts b/apps/namada-interface/src/store/mocks.ts index a7b8acc4b..48cc5f31b 100644 --- a/apps/namada-interface/src/store/mocks.ts +++ b/apps/namada-interface/src/store/mocks.ts @@ -10,21 +10,21 @@ export const mockAppState: RootState = { derived: { "namada-masp-1.5.32ccad5356012a7": { atest1v4ehgw36xqcyz3zrxsenzd3kxsunsvzzxymyywpkg4zrjv2pxepyyd3cgse5gwzxgsm5x3zrkf2pwp: - { - details: { - chainId: "namada-masp-1.5.32ccad5356012a7", - alias: "Namada", - address: - "atest1v4ehgw36xqcyz3zrxsenzd3kxsunsvzzxymyywpkg4zrjv2pxepyyd3cgse5gwzxgsm5x3zrkf2pwp", - isShielded: false, - type: AccountType.PrivateKey, + { + details: { + chainId: "namada-masp-1.5.32ccad5356012a7", + alias: "Namada", + address: + "atest1v4ehgw36xqcyz3zrxsenzd3kxsunsvzzxymyywpkg4zrjv2pxepyyd3cgse5gwzxgsm5x3zrkf2pwp", + isShielded: false, + type: AccountType.PrivateKey, + }, + balance: { + NAM: new BigNumber(1000), + ATOM: new BigNumber(1000), + ETH: new BigNumber(1000), + }, }, - balance: { - NAM: new BigNumber(1000), - ATOM: new BigNumber(1000), - ETH: new BigNumber(1000), - }, - }, }, "namada-test.1e670ba91369ec891fc": { "39UL18": { @@ -146,4 +146,7 @@ export const mockAppState: RootState = { toasts: {}, pendingActions: [], }, + proposals: { + proposals: [], + }, }; diff --git a/apps/namada-interface/src/store/store.ts b/apps/namada-interface/src/store/store.ts index c7f0a38f5..48319c3b2 100644 --- a/apps/namada-interface/src/store/store.ts +++ b/apps/namada-interface/src/store/store.ts @@ -11,6 +11,7 @@ import { coinsReducer, notificationsReducer, stakingAndGovernanceReducers, + proposalsReducer, } from "slices"; import { LocalStorageKeys } from "App/types"; import { createTransform } from "redux-persist"; @@ -42,6 +43,7 @@ const reducers = combineReducers({ coins: coinsReducer, notifications: notificationsReducer, stakingAndGovernance: stakingAndGovernanceReducers, + proposals: proposalsReducer, }); const persistConfig = { diff --git a/packages/shared/lib/src/query.rs b/packages/shared/lib/src/query.rs index e274dad46..7eafaa6d5 100644 --- a/packages/shared/lib/src/query.rs +++ b/packages/shared/lib/src/query.rs @@ -373,13 +373,19 @@ impl Query { } } + let proposal_type = match proposal_type { + ProposalType::ETHBridge => "eth_bridge", + ProposalType::PGFCouncil => "pgf_council", + ProposalType::Default(_) => "default", + }; + let res = ProposalInfo { id: id.to_string(), - proposal_type, + proposal_type: String::from(proposal_type), author, - start_epoch, - end_epoch, - grace_epoch, + start_epoch: start_epoch.to_string(), + end_epoch: end_epoch.to_string(), + grace_epoch: grace_epoch.to_string(), content, status: status.to_string(), yes_votes: yes_votes.map(|v| v.to_string()), @@ -477,11 +483,11 @@ struct Votes { #[derive(Serialize)] struct ProposalInfo { id: String, - proposal_type: ProposalType, + proposal_type: String, author: Address, - start_epoch: Epoch, - end_epoch: Epoch, - grace_epoch: Epoch, + start_epoch: String, + end_epoch: String, + grace_epoch: String, content: HashMap, status: String, yes_votes: Option, diff --git a/packages/shared/package.json b/packages/shared/package.json index 9a0dc9a05..22719aa77 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -14,6 +14,8 @@ "test-wasm:ci": "cd ./lib && cargo test" }, "dependencies": { + "fp-ts": "^2.16.1", + "io-ts": "^2.2.20", "typescript": "^5.1.3" }, "devDependencies": { diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index bb93d28f1..c51348b9a 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,5 +1,12 @@ +import { isLeft } from "fp-ts/Either"; +import { PathReporter } from "io-ts/PathReporter"; + +import { Proposals } from "./types"; +import type { Proposal } from "./types"; import { Query as RustQuery } from "./shared/shared"; +import { Type } from "io-ts"; export * from "./shared/shared"; +export * from "./types"; type TimeoutOpts = { // Timeout in miliseconds @@ -37,8 +44,20 @@ const promiseWithTimeout = }); }; +const validateData = (data: any, type: Type): T => { + const decoded = type.decode(data); + if (isLeft(decoded)) { + throw Error( + `Could not validate data: ${PathReporter.report(decoded).join("\n")}` + ); + } else { + return decoded.right; + } +}; + //Fallbacks for rust panics export class Query extends RustQuery { + _query_proposals = super.query_proposals.bind(this); query_balance = promiseWithTimeout(super.query_balance.bind(this), { timeout: 10000, }); @@ -50,9 +69,12 @@ export class Query extends RustQuery { super.query_my_validators.bind(this) ); get_proposal_votes = promiseWithTimeout(super.get_proposal_votes.bind(this)); - query_proposals = promiseWithTimeout(super.query_proposals.bind(this), { - timeout: 3000, - }); + query_proposals = async (): Promise => { + const fn = this._query_proposals; + const proposals = await fn(); + console.log(proposals); + return validateData(proposals, Proposals); + }; get_total_delegations = promiseWithTimeout( super.get_total_delegations.bind(this) ); diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts new file mode 100644 index 000000000..e7c9d4bc2 --- /dev/null +++ b/packages/shared/src/types.ts @@ -0,0 +1,34 @@ +import * as t from "io-ts"; + +const Content = t.type({ + abstract: t.string, + authors: t.string, + created: t.string, + details: t.string, + "discussions-to": t.string, + license: t.string, + motivation: t.string, + requires: t.string, + title: t.string, +}); + +export const Proposal = t.intersection([ + t.type({ + id: t.string, + proposal_type: t.string, + author: t.string, + start_epoch: t.string, + end_epoch: t.string, + grace_epoch: t.string, + content: Content, + status: t.string, + }), + t.partial({ + yes_votes: t.string, + total_voting_power: t.string, + result: t.string, + }), +]); + +export const Proposals = t.array(Proposal); +export type Proposal = t.TypeOf; diff --git a/yarn.lock b/yarn.lock index ff3ca30cf..79959cf0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9867,6 +9867,11 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fp-ts@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.1.tgz#6abc401ce42b65364ca8f0b0d995c5840c68a930" + integrity sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA== + fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" @@ -10615,6 +10620,11 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== +io-ts@^2.2.20: + version "2.2.20" + resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.20.tgz#be42b75f6668a2c44f706f72ee6e4c906777c7f5" + integrity sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA== + ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"