Skip to content

Commit

Permalink
feat: add useDAOProposalById (#1317)
Browse files Browse the repository at this point in the history
* feat: add useDAOProposalById

* lint: remove unused export

* chore: simplify error title

* chore: factorize code from useDAOProposals and useDAOProposalById

---------

Co-authored-by: n0izn0iz <[email protected]>
  • Loading branch information
WaDadidou and n0izn0iz authored Oct 28, 2024
1 parent e7d12b0 commit 163cbad
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 49 deletions.
148 changes: 148 additions & 0 deletions packages/hooks/dao/useDAOProposalById.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { GnoJSONRPCProvider } from "@gnolang/gno-js-client";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useCallback } from "react";

import { useDAOFirstProposalModule } from "./useDAOProposalModules";
import {
cosmwasmToAppProposal,
GnoDAOProposal,
gnoToAppProposal,
} from "./useDAOProposals";

import { useFeedbacks } from "@/context/FeedbacksProvider";
import { DaoProposalSingleQueryClient } from "@/contracts-clients/dao-proposal-single/DaoProposalSingle.client";
import {
NetworkKind,
mustGetNonSigningCosmWasmClient,
parseUserId,
} from "@/networks";
import { extractGnoJSONString } from "@/utils/gno";

const daoProposalByIdQueryKey = (
daoId: string | undefined,
proposalId: number | undefined,
) => ["dao-proposals", daoId, proposalId];

export const useDAOProposalById = (
daoId: string | undefined,
proposalId: number | undefined,
) => {
const { setToast } = useFeedbacks();
const [network, daoAddress] = parseUserId(daoId);
const { daoProposal: cosmWasmDAOProposal, ...cosmWasmOther } =
useCosmWasmDAOProposalById(daoId, proposalId);

const { data: gnoDAOProposal, ...gnoOther } = useQuery(
daoProposalByIdQueryKey(daoId, proposalId),
async () => {
try {
if (network?.kind !== NetworkKind.Gno)
throw new Error("Not a Gno network");
if (!proposalId) throw new Error("Missing proposal id");

const provider = new GnoJSONRPCProvider(network.endpoint);

const gnoProposal: GnoDAOProposal = extractGnoJSONString(
await provider.evaluateExpression(
daoAddress,
`getProposalJSON(0, ${proposalId})`,
),
);

return gnoToAppProposal(gnoProposal);
} catch (err) {
const title =
"Failed to fetch the Gno DAO proposal\nThis proposal might not exist in this DAO";
const message = err instanceof Error ? err.message : `${err}`;
setToast({
title,
message,
type: "error",
mode: "normal",
});
console.error(title, message);
return null;
}
},
{
staleTime: Infinity,
enabled: !!(daoId && network?.kind === NetworkKind.Gno && proposalId),
},
);

if (network?.kind === NetworkKind.Gno) {
return {
daoProposal: gnoDAOProposal,
...gnoOther,
};
}

return {
daoProposal: cosmWasmDAOProposal,
...cosmWasmOther,
};
};

const useCosmWasmDAOProposalById = (
daoId: string | undefined,
proposalId: number | undefined,
) => {
const { setToast } = useFeedbacks();
const [network] = parseUserId(daoId);
const networkId = network?.id;
const { daoFirstProposalModule } = useDAOFirstProposalModule(daoId);
const proposalModuleAddress = daoFirstProposalModule?.address;

const { data, ...other } = useQuery(
daoProposalByIdQueryKey(daoId, proposalId),
async () => {
try {
if (!networkId) throw new Error("Missing network id");
if (!proposalModuleAddress)
throw new Error("No proposal module address");
if (!proposalId) throw new Error("Missing proposal id");

const cosmwasmClient = await mustGetNonSigningCosmWasmClient(networkId);
const daoProposalClient = new DaoProposalSingleQueryClient(
cosmwasmClient,
proposalModuleAddress,
);

const daoProposal = await daoProposalClient.proposal({
proposalId,
});

return cosmwasmToAppProposal(daoProposal);
} catch (err) {
const title =
"Failed to fetch the Cosmos DAO proposal\nThis proposal might not exist in this DAO";
const message = err instanceof Error ? err.message : `${err}`;
setToast({
title,
message,
type: "error",
mode: "normal",
});
console.error(title, message);
return null;
}
},
{
staleTime: Infinity,
enabled: !!(networkId && proposalModuleAddress && proposalId),
},
);
return { daoProposal: data, ...other };
};

export const useInvalidateDAOProposalById = (
daoId: string | undefined,
proposalId: number | undefined,
) => {
const queryClient = useQueryClient();
return useCallback(
() =>
queryClient.invalidateQueries(daoProposalByIdQueryKey(daoId, proposalId)),
[queryClient, daoId, proposalId],
);
};
110 changes: 61 additions & 49 deletions packages/hooks/dao/useDAOProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type GnoProposalVotes = {
abstain: number;
};

type GnoDAOProposal = {
export type GnoDAOProposal = {
id: number;
title: string;
description: string;
Expand Down Expand Up @@ -67,47 +67,7 @@ export const useDAOProposals = (daoId: string | undefined) => {

for (let i = 0; i < gnoProposals.length; i++) {
const prop = gnoProposals[i];
const title = prop.title;
const description = prop.description;
const status = prop.status.toLowerCase() as Status;
const proposer = prop.proposer;
const yesVotes = prop.votes.yes;
const noVotes = prop.votes.no;
const abstainVotes = prop.votes.abstain;
const threshold =
prop.threshold.thresholdQuorum.threshold.percent / 10000;
const quorum = prop.threshold.thresholdQuorum.quorum.percent / 10000;
const actions = prop.messages.map((m) => JSON.stringify(m));
// TODO: render actions
proposals.push({
id: i,
proposal: {
title,
description,
votes: {
yes: yesVotes.toString(),
no: noVotes.toString(),
abstain: abstainVotes.toString(),
},
allow_revoting: false,
expiration: "TODO" as any,
msgs: prop.messages.map((m) => ({
...m,
gno: true,
})),
actions,
proposer,
start_height: prop.startHeight,
status,
threshold: {
threshold_quorum: {
threshold: { percent: `${threshold}` },
quorum: { percent: `${quorum}` },
},
},
total_power: prop.totalPower.toString(),
},
});
proposals.push(gnoToAppProposal(prop));
}
return proposals;
},
Expand Down Expand Up @@ -153,13 +113,7 @@ const useCosmWasmDAOProposals = (daoId: string | undefined) => {
});
if (listProposals.proposals.length === 0) break;
allProposals.push(
...listProposals.proposals.map((p) => ({
...p,
proposal: {
...p.proposal,
actions: [] as string[],
},
})),
...listProposals.proposals.map((p) => cosmwasmToAppProposal(p)),
);
startAfter += listProposals.proposals.length;
}
Expand All @@ -178,3 +132,61 @@ export const useInvalidateDAOProposals = (daoId: string | undefined) => {
[queryClient, daoId],
);
};

export const gnoToAppProposal = (proposal: GnoDAOProposal) => {
// TODO: render actions
const title = proposal.title;
const description = proposal.description;
const status = proposal.status.toLowerCase() as Status;
const proposer = proposal.proposer;
const yesVotes = proposal.votes.yes;
const noVotes = proposal.votes.no;
const abstainVotes = proposal.votes.abstain;
const threshold =
proposal.threshold.thresholdQuorum.threshold.percent / 10000;
const quorum = proposal.threshold.thresholdQuorum.quorum.percent / 10000;
const actions = proposal.messages.map((m) => JSON.stringify(m));

const appProposal: AppProposalResponse = {
id: proposal.id,
proposal: {
title,
description,
votes: {
yes: yesVotes.toString(),
no: noVotes.toString(),
abstain: abstainVotes.toString(),
},
allow_revoting: false,
expiration: "TODO" as any,
msgs: proposal.messages.map((m) => ({
...m,
gno: true,
})),
actions,
proposer,
start_height: proposal.startHeight,
status,
threshold: {
threshold_quorum: {
threshold: { percent: `${threshold}` },
quorum: { percent: `${quorum}` },
},
},
total_power: proposal.totalPower.toString(),
},
};

return appProposal;
};

export const cosmwasmToAppProposal = (proposal: ProposalResponse) => {
const appPrpoposal: AppProposalResponse = {
...proposal,
proposal: {
...proposal.proposal,
actions: [] as string[],
},
};
return appPrpoposal;
};

0 comments on commit 163cbad

Please sign in to comment.