Skip to content

Commit

Permalink
feat: APP-3028 - Update Voting process to use new standard transactio…
Browse files Browse the repository at this point in the history
…n flow (#1321)
  • Loading branch information
sepehr2github authored Apr 22, 2024
1 parent ce7c5dd commit 098707e
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 380 deletions.
16 changes: 16 additions & 0 deletions src/assets/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1850,5 +1850,21 @@
"approve": "Execute now",
"success": "Continue to proposal"
}
},
"voteOrApprovalDialog": {
"title": {
"vote": "Sign Vote",
"approval": "Approve"
},
"button": {
"vote": {
"success": "Continue to proposal",
"approve": "Vote now"
},
"approval": {
"success": "Continue to proposal",
"approve": "Approve proposal"
}
}
}
}
4 changes: 4 additions & 0 deletions src/containers/voteOrApprovalDialog/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
useSendVoteOrApprovalTransaction,
type IUseSendVoteOrApprovalTransaction,
} from './useSendVoteOrApprovalTransaction';
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import {useSendTransaction} from 'hooks/useSendTransaction';
import {ITransaction} from 'services/transactions/domain/transaction';
import {useNetwork} from 'context/network';
import {PluginTypes} from 'hooks/usePluginClient';
import {useWallet} from 'hooks/useWallet';
import {voteStorage} from 'utils/localStorage';
import {CHAIN_METADATA} from 'utils/constants';
import {useParams} from 'react-router-dom';
import {constants} from 'ethers';
import {
MultisigProposal,
TokenVotingProposal,
VoteValues,
} from '@aragon/sdk-client';
import {useQueryClient} from '@tanstack/react-query';
import {
AragonSdkQueryItem,
aragonSdkQueryKeys,
} from 'services/aragon-sdk/query-keys';
import {usePastVotingPower} from 'services/aragon-sdk/queries/use-past-voting-power';
import {GaslessVotingProposal} from '@vocdoni/gasless-voting';
import {ProposalStatus} from '@aragon/sdk-client-common';
import {useDaoDetailsQuery} from 'hooks/useDaoDetails';
import {useDaoToken} from 'hooks/useDaoToken';

export interface IUseSendVoteOrApprovalTransaction {
/**
* Process name for logging.
*/
process: string;
/**
* Vote transaction to be sent.
*/
transaction?: ITransaction;
/**
* Proposal to vote for.
*/
proposal: MultisigProposal | TokenVotingProposal | GaslessVotingProposal;
/**
* Defines if the vote should be replaced.
*/
replacingVote?: boolean;
/**
* Vote to be sent.
*/
vote?: VoteValues;
/**
* Callback called on vote/approve submitted.
*/
setVoteOrApprovalSubmitted: (value: boolean) => void;
}

export const useSendVoteOrApprovalTransaction = (
params: IUseSendVoteOrApprovalTransaction
) => {
const {
process,
transaction,
replacingVote,
vote,
proposal,
setVoteOrApprovalSubmitted,
} = params;
const {network} = useNetwork();
const {address} = useWallet();
const queryClient = useQueryClient();

const {id: proposalId = ''} = useParams();

const {data: daoDetails} = useDaoDetailsQuery();
const pluginAddress = daoDetails?.plugins?.[0]?.instanceAddress;
const pluginType = daoDetails?.plugins?.[0]?.id as PluginTypes;
const {data: daoToken} = useDaoToken(pluginAddress);

const shouldFetchPastVotingPower =
address != null &&
daoToken != null &&
proposal != null &&
proposal.status === ProposalStatus.ACTIVE;

const {data: votingPower = constants.Zero} = usePastVotingPower(
{
address: address as string,
tokenAddress: daoToken?.address as string,
blockNumber: proposal?.creationBlockNumber as number,
network,
},
{
enabled: shouldFetchPastVotingPower,
}
);

const handleVoteOrApprovalSuccess = () => {
setVoteOrApprovalSubmitted(true);

switch (pluginType) {
case 'token-voting.plugin.dao.eth': {
// cache token-voting vote
if (address != null && votingPower && vote) {
// fetch token user balance, ie vote weight
try {
const voteToPersist = {
address: address.toLowerCase(),
vote: vote,
weight: votingPower.toBigInt(),
voteReplaced: !!replacingVote,
};

// store in local storage
voteStorage.addVote(
CHAIN_METADATA[network].id,
proposalId.toString(),
voteToPersist
);
} catch (error) {
console.error(error);
}
}
break;
}
case 'multisig.plugin.dao.eth': {
if (address) {
voteStorage.addVote(
CHAIN_METADATA[network].id,
proposalId,
address.toLowerCase()
);
}
break;
}
case 'vocdoni-gasless-voting-poc-vanilla-erc20.plugin.dao.eth': {
break;
}
default: {
break;
}
}

const allProposalsQuery = [AragonSdkQueryItem.PROPOSALS];
const currentProposal = aragonSdkQueryKeys.proposal({
id: proposalId,
pluginType,
});

queryClient.invalidateQueries({
queryKey: allProposalsQuery,
});
queryClient.invalidateQueries({
queryKey: currentProposal,
});
};

const sendTransactionResults = useSendTransaction({
logContext: {stack: [process]},
transaction,
onSuccess: handleVoteOrApprovalSuccess,
});

return sendTransactionResults;
};
1 change: 1 addition & 0 deletions src/containers/voteOrApprovalDialog/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {voteOrApprovalUtils} from './voteOrApprovalUtils';
45 changes: 45 additions & 0 deletions src/containers/voteOrApprovalDialog/utils/voteOrApprovalUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
ApproveMultisigProposalParams,
VoteProposalParams,
VoteValues,
} from '@aragon/sdk-client';
import {PluginTypes} from 'hooks/usePluginClient';
import {useParams} from 'react-router-dom';
import {ProposalId} from 'utils/types';

class VoteOrApprovalUtils {
buildVoteOrApprovalParams = (
PluginType: PluginTypes,
tryExecution: boolean,
vote?: VoteValues
) => {
let param: VoteProposalParams | ApproveMultisigProposalParams | undefined =
undefined;
const {id: urlId} = useParams();
const proposalId = new ProposalId(urlId!).export();

switch (PluginType) {
case 'token-voting.plugin.dao.eth':
case 'vocdoni-gasless-voting-poc-vanilla-erc20.plugin.dao.eth': {
param = {
proposalId,
vote: vote as VoteValues,
};
break;
}
case 'multisig.plugin.dao.eth': {
param = {
proposalId,
tryExecution,
};
break;
}
default:
throw new Error(`Unknown plugin type`);
}

return param;
};
}

export const voteOrApprovalUtils = new VoteOrApprovalUtils();
96 changes: 96 additions & 0 deletions src/containers/voteOrApprovalDialog/voteOrApprovalDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import {ModalProps} from '@aragon/ods-old';
import {TransactionDialog} from 'containers/transactionDialog';
import {useVoteOrApprovalTransaction} from 'services/transactions/queries/useVoteOrApprovalTransaction';
import {voteOrApprovalUtils} from './utils/index';
import {IBuildVoteOrApprovalTransactionParams} from 'services/transactions/transactionsService.api';
import {useSendVoteOrApprovalTransaction} from './hooks';
import {useTranslation} from 'react-i18next';
import {PluginTypes, usePluginClient} from 'hooks/usePluginClient';
import {
MultisigProposal,
TokenVotingProposal,
VoteValues,
} from '@aragon/sdk-client';
import {GaslessVotingProposal} from '@vocdoni/gasless-voting';
import {useDaoDetailsQuery} from 'hooks/useDaoDetails';

/**
* Represents the props for the VoteOrApprovalDialog component.
*/
export interface IVoteOrApprovalDialogProps extends ModalProps {
tryExecution: boolean;
vote?: VoteValues;
replacingVote?: boolean;
setVoteOrApprovalSubmitted: (value: boolean) => void;
proposal: MultisigProposal | TokenVotingProposal | GaslessVotingProposal;
}

const voteOrApprovalProcess = 'VOTE_OR_APPROVAL';

export const VoteOrApprovalDialog: React.FC<
IVoteOrApprovalDialogProps
> = props => {
const {
isOpen,
onClose,
tryExecution,
replacingVote,
vote,
proposal,
setVoteOrApprovalSubmitted,
...otherProps
} = props;

const {data: daoDetails} = useDaoDetailsQuery();
const pluginType = daoDetails?.plugins?.[0]?.id as PluginTypes;

const {t} = useTranslation();
const pluginClient = usePluginClient(pluginType as PluginTypes);

const voteOrApprovalParams = voteOrApprovalUtils.buildVoteOrApprovalParams(
pluginType,
tryExecution,
vote
);

const {data: transaction} = useVoteOrApprovalTransaction(
{
...voteOrApprovalParams,
pluginClient,
} as IBuildVoteOrApprovalTransactionParams,
{enabled: voteOrApprovalParams != null && pluginClient != null}
);

const sendTransactionResults = useSendVoteOrApprovalTransaction({
process: voteOrApprovalProcess,
transaction,
replacingVote,
vote,
proposal,
setVoteOrApprovalSubmitted,
});

const handleSuccessClick = () => onClose?.();

const dialogType =
pluginType === 'multisig.plugin.dao.eth' ? 'approval' : 'vote';

return (
<TransactionDialog
title={t(`voteOrApprovalDialog.title.${dialogType}`)}
isOpen={isOpen}
sendTransactionResult={sendTransactionResults}
displayTransactionStatus={transaction != null}
sendTransactionLabel={t(
`voteOrApprovalDialog.button.${dialogType}.approve`
)}
successButton={{
label: t(`voteOrApprovalDialog.button.${dialogType}.success`),
onClick: handleSuccessClick,
}}
onClose={onClose}
{...otherProps}
/>
);
};
Loading

0 comments on commit 098707e

Please sign in to comment.