Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add useDAOProposalById #1317

Merged
merged 6 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
};
Loading