-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: APP-3028 - Update Voting process to use new standard transactio…
…n flow (#1321)
- Loading branch information
1 parent
ce7c5dd
commit 098707e
Showing
12 changed files
with
422 additions
and
380 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { | ||
useSendVoteOrApprovalTransaction, | ||
type IUseSendVoteOrApprovalTransaction, | ||
} from './useSendVoteOrApprovalTransaction'; |
160 changes: 160 additions & 0 deletions
160
src/containers/voteOrApprovalDialog/hooks/useSendVoteOrApprovalTransaction.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {voteOrApprovalUtils} from './voteOrApprovalUtils'; |
45 changes: 45 additions & 0 deletions
45
src/containers/voteOrApprovalDialog/utils/voteOrApprovalUtils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
96
src/containers/voteOrApprovalDialog/voteOrApprovalDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
/> | ||
); | ||
}; |
Oops, something went wrong.