From 25a44c7267ca0ebfa47632be989f935b5623e0a5 Mon Sep 17 00:00:00 2001 From: agrippa Date: Thu, 19 Oct 2023 15:12:31 -0400 Subject: [PATCH] Revert "Delegation rework pt 2" (#1884) --- .gitignore | 2 - .vscode/settings.json | 12 ++ actions/castVote.ts | 169 +++--------------- .../GovernancePower/GovernancePowerCard.tsx | 10 ++ components/SelectPrimaryDelegators.tsx | 20 ++- components/VoteCommentModal.tsx | 31 ++-- components/VotePanel/CastMultiVoteButtons.tsx | 85 +++++---- components/VotePanel/CastVoteButtons.tsx | 54 +++++- components/VotePanel/VetoButtons.tsx | 8 +- components/VotePanel/useCanVote.ts | 107 ----------- constants/flags.ts | 14 -- constants/plugins.ts | 18 +- hooks/queries/governancePower.ts | 25 ++- hooks/queries/proposal.ts | 12 +- hooks/useSubmitVote.ts | 47 +---- 15 files changed, 199 insertions(+), 415 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 components/VotePanel/useCanVote.ts diff --git a/.gitignore b/.gitignore index 55713a92f9..97a8637876 100644 --- a/.gitignore +++ b/.gitignore @@ -43,5 +43,3 @@ yarn-error.log* # Sentry .sentryclirc - -.vscode/settings.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..92a022a112 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "cSpell.words": [ + "Addin", + "blockworks", + "lamports", + "solana", + "VERCEL", + "WSOL" + ], + "editor.formatOnSave": true +} diff --git a/actions/castVote.ts b/actions/castVote.ts index 8e13367dbb..0dd54fbb2c 100644 --- a/actions/castVote.ts +++ b/actions/castVote.ts @@ -1,7 +1,7 @@ import { - Connection, Keypair, PublicKey, + Transaction, TransactionInstruction, } from '@solana/web3.js' import { @@ -14,7 +14,6 @@ import { VoteKind, VoteType, withPostChatMessage, - withCreateTokenOwnerRecord, } from '@solana/spl-governance' import { ProgramAccount } from '@solana/spl-governance' import { RpcContext } from '@solana/spl-governance' @@ -29,15 +28,11 @@ import { SequenceType, txBatchesToInstructionSetWithSigners, } from '@utils/sendTransactions' +import { sendTransaction } from '@utils/send' import { calcCostOfNftVote, checkHasEnoughSolToVote } from '@tools/nftVoteCalc' import useNftProposalStore from 'NftVotePlugin/NftProposalStore' import { HeliumVsrClient } from 'HeliumVotePlugin/sdk/client' import { NftVoterClient } from '@utils/uiTypes/NftVoterClient' -import { fetchRealmByPubkey } from '@hooks/queries/realm' -import { fetchProposalByPubkeyQuery } from '@hooks/queries/proposal' -import { findPluginName } from '@hooks/queries/governancePower' -import { DELEGATOR_BATCH_VOTE_SUPPORT_BY_PLUGIN } from '@constants/flags' -import { fetchTokenOwnerRecordByPubkey } from '@hooks/queries/tokenOwnerRecord' import { fetchProgramVersion } from '@hooks/queries/useProgramVersionQuery' const getVetoTokenMint = ( @@ -55,86 +50,6 @@ const getVetoTokenMint = ( return vetoTokenMint } -const createDelegatorVote = async ({ - connection, - realmPk, - proposalPk, - tokenOwnerRecordPk, - userPk, - vote, -}: { - connection: Connection - realmPk: PublicKey - proposalPk: PublicKey - tokenOwnerRecordPk: PublicKey - userPk: PublicKey - vote: Vote -}) => { - // - const realm = (await fetchRealmByPubkey(connection, realmPk)).result - if (!realm) throw new Error() - const proposal = (await fetchProposalByPubkeyQuery(connection, proposalPk)) - .result - if (!proposal) throw new Error() - - const programVersion = await fetchProgramVersion(connection, realm.owner) - - const castVoteIxs: TransactionInstruction[] = [] - await withCastVote( - castVoteIxs, - realm.owner, - programVersion, - realm.pubkey, - proposal.account.governance, - proposal.pubkey, - proposal.account.tokenOwnerRecord, - tokenOwnerRecordPk, - userPk, - proposal.account.governingTokenMint, - vote, - userPk - //plugin?.voterWeightPk, - //plugin?.maxVoterWeightRecord - ) - return castVoteIxs -} - -const createTokenOwnerRecordIfNeeded = async ({ - connection, - realmPk, - tokenOwnerRecordPk, - payer, - governingTokenMint, -}: { - connection: Connection - realmPk: PublicKey - tokenOwnerRecordPk: PublicKey - payer: PublicKey - governingTokenMint: PublicKey -}) => { - const realm = await fetchRealmByPubkey(connection, realmPk) - if (!realm.result) throw new Error() - const version = await fetchProgramVersion(connection, realm.result.owner) - - const tokenOwnerRecord = await fetchTokenOwnerRecordByPubkey( - connection, - tokenOwnerRecordPk - ) - if (tokenOwnerRecord.result) return [] - // create token owner record - const ixs: TransactionInstruction[] = [] - await withCreateTokenOwnerRecord( - ixs, - realm.result.owner, - version, - realmPk, - payer, - governingTokenMint, - payer - ) - return ixs -} - export async function castVote( { connection, wallet, programId, walletPubkey }: RpcContext, realm: ProgramAccount, @@ -145,9 +60,9 @@ export async function castVote( votingPlugin?: VotingClient, runAfterConfirmation?: (() => void) | null, voteWeights?: number[], - additionalTokenOwnerRecords?: PublicKey[] + _additionalTokenOwnerRecords?: [] ) { - const chatMessageSigners: Keypair[] = [] + const signers: Keypair[] = [] const createCastNftVoteTicketIxs: TransactionInstruction[] = [] const createPostMessageTicketIxs: TransactionInstruction[] = [] @@ -166,6 +81,7 @@ export async function castVote( tokenOwnerRecord, createCastNftVoteTicketIxs ) + console.log('PLUGIN IXS', pluginCastVoteIxs) const isMulti = proposal.account.voteType !== VoteType.SINGLE_CHOICE && @@ -237,25 +153,6 @@ export async function castVote( plugin?.maxVoterWeightRecord ) - const delegatorCastVoteAtoms = - additionalTokenOwnerRecords && - DELEGATOR_BATCH_VOTE_SUPPORT_BY_PLUGIN[ - findPluginName(votingPlugin?.client?.program.programId) - ] - ? await Promise.all( - additionalTokenOwnerRecords.map((tokenOwnerRecordPk) => - createDelegatorVote({ - connection, - realmPk: realm.pubkey, - proposalPk: proposal.pubkey, - tokenOwnerRecordPk, - userPk: walletPubkey, - vote, - }) - ) - ) - : [] - const pluginPostMessageIxs: TransactionInstruction[] = [] const postMessageIxs: TransactionInstruction[] = [] if (message) { @@ -268,7 +165,7 @@ export async function castVote( await withPostChatMessage( postMessageIxs, - chatMessageSigners, + signers, GOVERNANCE_CHAT_PROGRAM_ID, programId, realm.pubkey, @@ -285,48 +182,22 @@ export async function castVote( const isNftVoter = votingPlugin?.client instanceof NftVoterClient const isHeliumVoter = votingPlugin?.client instanceof HeliumVsrClient - const tokenOwnerRecordIxs = await createTokenOwnerRecordIfNeeded({ - connection, - realmPk: realm.pubkey, - tokenOwnerRecordPk: tokenOwnerRecord, - payer, - governingTokenMint: tokenMint, - }) if (!isNftVoter && !isHeliumVoter) { - const batch1 = [ - ...tokenOwnerRecordIxs, - ...pluginCastVoteIxs, - ...castVoteIxs, - ...pluginPostMessageIxs, - ...postMessageIxs, - ] - // chunk size chosen conservatively. "Atoms" refers to atomic clusters of instructions (namely, updatevoterweight? + vote) - const delegatorBatches = chunks(delegatorCastVoteAtoms, 2).map((x) => - x.flat() + const transaction = new Transaction() + transaction.add( + ...[ + ...pluginCastVoteIxs, + ...castVoteIxs, + ...pluginPostMessageIxs, + ...postMessageIxs, + ] ) - const actions = [batch1, ...delegatorBatches].map((ixs) => ({ - instructionsSet: ixs.map((ix) => ({ - transactionInstruction: ix, - signers: chatMessageSigners.filter((kp) => - ix.keys.find((key) => key.isSigner && key.pubkey.equals(kp.publicKey)) - ), - })), - sequenceType: SequenceType.Parallel, - })) - await sendTransactionsV3({ - connection, - wallet, - transactionInstructions: actions, - callbacks: { - afterAllTxConfirmed: () => { - if (runAfterConfirmation) { - runAfterConfirmation() - } - }, - }, - }) + await sendTransaction({ transaction, wallet, connection, signers }) + if (runAfterConfirmation) { + runAfterConfirmation() + } } // we need to chunk instructions @@ -346,7 +217,7 @@ export async function castVote( return { instructionsSet: txBatchesToInstructionSetWithSigners( txBatch, - message ? [[], chatMessageSigners] : [], // seeing signer related bugs when posting chat? This is likely culprit + message ? [[], signers] : [], batchIdx ), sequenceType: SequenceType.Sequential, @@ -403,7 +274,7 @@ export async function castVote( return { instructionsSet: txBatchesToInstructionSetWithSigners( txBatch, - message ? [[], chatMessageSigners] : [], // seeing signer related bugs when posting chat? This is likely culprit + message ? [[], signers] : [], batchIdx ), sequenceType: SequenceType.Sequential, diff --git a/components/GovernancePower/GovernancePowerCard.tsx b/components/GovernancePower/GovernancePowerCard.tsx index 795be27064..f5d54b0d7f 100644 --- a/components/GovernancePower/GovernancePowerCard.tsx +++ b/components/GovernancePower/GovernancePowerCard.tsx @@ -38,6 +38,12 @@ const GovernancePowerCard = () => { const bothLoading = communityPower.loading && councilPower.loading + const bothZero = + communityPower.result !== undefined && + councilPower.result !== undefined && + communityPower.result.isZero() && + councilPower.result.isZero() + const realmConfig = useRealmConfigQuery().data?.result return ( @@ -52,6 +58,10 @@ const GovernancePowerCard = () => {
+ ) : bothZero ? ( +
+ You do not have any governance power in this dao +
) : (
{realmConfig?.account.communityTokenConfig.tokenType === diff --git a/components/SelectPrimaryDelegators.tsx b/components/SelectPrimaryDelegators.tsx index 1c926dd7c8..d3af4a2bb5 100644 --- a/components/SelectPrimaryDelegators.tsx +++ b/components/SelectPrimaryDelegators.tsx @@ -1,13 +1,14 @@ +import { DisplayAddress } from '@cardinal/namespaces-components' import Select from '@components/inputs/Select' import useWalletOnePointOh from '@hooks/useWalletOnePointOh' import { useTokenOwnerRecordsDelegatedToUser } from '@hooks/queries/tokenOwnerRecord' import { useSelectedDelegatorStore } from 'stores/useSelectedDelegatorStore' import { PublicKey } from '@solana/web3.js' +import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext' import { useRealmQuery } from '@hooks/queries/realm' import { useMemo } from 'react' import { ProgramAccount, TokenOwnerRecord } from '@solana/spl-governance' import { capitalize } from '@utils/helpers' -import { abbreviateAddress } from '@utils/formatting' const YOUR_WALLET_VALUE = 'Your wallet' @@ -103,6 +104,7 @@ function PrimaryDelegatorSelect({ kind: 'community' | 'council' tors: ProgramAccount[] }) { + const connection = useLegacyConnectionContext() return (
@@ -116,7 +118,13 @@ function PrimaryDelegatorSelect({ componentLabel={ selectedDelegator ? (
- {abbreviateAddress(selectedDelegator)} +
) : ( @@ -133,7 +141,13 @@ function PrimaryDelegatorSelect({ value={delegatedTor.account.governingTokenOwner.toBase58()} >
- {abbreviateAddress(delegatedTor.account.governingTokenOwner)} +
diff --git a/components/VoteCommentModal.tsx b/components/VoteCommentModal.tsx index e5ea1e1c9d..362a6ff4fb 100644 --- a/components/VoteCommentModal.tsx +++ b/components/VoteCommentModal.tsx @@ -7,12 +7,15 @@ import Loading from './Loading' import Modal from './Modal' import Input from './inputs/Input' import Tooltip from './Tooltip' +import { TokenOwnerRecord } from '@solana/spl-governance' +import { ProgramAccount } from '@solana/spl-governance' import { useSubmitVote } from '@hooks/useSubmitVote' interface VoteCommentModalProps { onClose: () => void isOpen: boolean vote: VoteKind + voterTokenRecord: ProgramAccount isMulti?: number[] } @@ -27,6 +30,7 @@ const VoteCommentModal: FunctionComponent = ({ onClose, isOpen, vote, + voterTokenRecord, isMulti, }) => { const [comment, setComment] = useState('') @@ -37,8 +41,9 @@ const VoteCommentModal: FunctionComponent = ({ const handleSubmit = async () => { await submitVote({ vote, + voterTokenRecord, comment, - voteWeights: isMulti, + voteWeights: isMulti }) onClose() @@ -73,20 +78,16 @@ const VoteCommentModal: FunctionComponent = ({ onClick={handleSubmit} >
- {!submitting && isMulti ? ( - '' - ) : vote === VoteKind.Approve ? ( - - ) : vote === VoteKind.Deny ? ( - - ) : ( - - )} - {submitting ? ( - - ) : ( - Vote {isMulti ? '' : voteString} - )} + {!submitting && + isMulti ? "" : + (vote === VoteKind.Approve ? ( + + ) : vote === VoteKind.Deny ? ( + + ) : ( + + ))} + {submitting ? : Vote {isMulti ? "" : voteString}}
diff --git a/components/VotePanel/CastMultiVoteButtons.tsx b/components/VotePanel/CastMultiVoteButtons.tsx index 8fd6511780..3d73af57fd 100644 --- a/components/VotePanel/CastMultiVoteButtons.tsx +++ b/components/VotePanel/CastMultiVoteButtons.tsx @@ -1,32 +1,35 @@ import { Proposal, VoteKind } from '@solana/spl-governance' -import { CheckCircleIcon } from '@heroicons/react/solid' +import { CheckCircleIcon } from "@heroicons/react/solid"; import { useState } from 'react' import Button, { SecondaryButton } from '../Button' import VoteCommentModal from '../VoteCommentModal' -import { useIsVoting, useVotingPop } from './hooks' +import { + useIsVoting, + useVoterTokenRecord, + useVotingPop, +} from './hooks' import { useProposalVoteRecordQuery } from '@hooks/queries/voteRecord' import { useSubmitVote } from '@hooks/useSubmitVote' import { useSelectedRealmInfo } from '@hooks/selectedRealm/useSelectedRealmRegistryEntry' -import { useCanVote } from './useCanVote' +import { useCanVote } from './CastVoteButtons' -export const CastMultiVoteButtons = ({ proposal }: { proposal: Proposal }) => { +export const CastMultiVoteButtons = ({proposal} : {proposal: Proposal}) => { const [showVoteModal, setShowVoteModal] = useState(false) const [vote, setVote] = useState<'yes' | 'no' | null>(null) const realmInfo = useSelectedRealmInfo() const allowDiscussion = realmInfo?.allowDiscussion ?? true const { submitting, submitVote } = useSubmitVote() const votingPop = useVotingPop() + const voterTokenRecord = useVoterTokenRecord() const [canVote, tooltipContent] = useCanVote() const { data: ownVoteRecord } = useProposalVoteRecordQuery('electoral') - const [selectedOptions, setSelectedOptions] = useState([]) - const [optionStatus, setOptionStatus] = useState( - new Array(proposal.options.length).fill(false) - ) + const [selectedOptions, setSelectedOptions] = useState([]); + const [optionStatus, setOptionStatus] = useState(new Array(proposal.options.length).fill(false)); const isVoteCast = !!ownVoteRecord?.found const isVoting = useIsVoting() - const nota = '$$_NOTA_$$' - const last = proposal.options.length - 1 + const nota = "$$_NOTA_$$"; + const last = proposal.options.length - 1; const handleVote = async (vote: 'yes' | 'no') => { setVote(vote) @@ -36,50 +39,51 @@ export const CastMultiVoteButtons = ({ proposal }: { proposal: Proposal }) => { } else { await submitVote({ vote: vote === 'yes' ? VoteKind.Approve : VoteKind.Deny, - voteWeights: selectedOptions, + voterTokenRecord: voterTokenRecord!, + voteWeights: selectedOptions }) } } const handleOption = (index: number) => { - let options = [...selectedOptions] - let status = [...optionStatus] - const isNota = proposal.options[last].label === nota + let options = [...selectedOptions]; + let status = [...optionStatus]; + const isNota = proposal.options[last].label === nota; - const selected = status[index] + const selected = status[index]; if (selected) { - options = options.filter((option) => option !== index) + options = options.filter(option => option !== index); status[index] = false } else { if (isNota) { if (index === last) { // if nota is clicked, unselect all other options - status = status.map(() => false) - status[index] = true - options = [index] + status = status.map(() => false); + status[index] = true; + options = [index]; } else { // remove nota from the selected if any other option is clicked - status[last] = false - options = options.filter((option) => option !== last) + status[last] = false; + options = options.filter(option => option !== last); if (!options.includes(index)) { options.push(index) } - status[index] = true + status[index] = true; } } else { if (!options.includes(index)) { options.push(index) } - status[index] = true + status[index] = true; } } - setSelectedOptions(options) - setOptionStatus(status) + setSelectedOptions(options); + setOptionStatus(status); } - return isVoting && !isVoteCast ? ( + return (isVoting && !isVoteCast) ? (

Cast your {votingPop} vote

@@ -95,35 +99,29 @@ export const CastMultiVoteButtons = ({ proposal }: { proposal: Proposal }) => { handleOption(index)} disabled={!canVote || submitting} isLoading={submitting} > - {optionStatus[index] && ( - - )} - {option.label === nota && index === last - ? 'None of the Above' - : option.label} + {optionStatus[index] && } + {option.label === nota && index === last ? "None of the Above" : option.label}
- ) - })} + )} + )}
Note: You can select one or more options
) : null -} +} \ No newline at end of file diff --git a/components/VotePanel/CastVoteButtons.tsx b/components/VotePanel/CastVoteButtons.tsx index 97b45d944e..5ade48dafb 100644 --- a/components/VotePanel/CastVoteButtons.tsx +++ b/components/VotePanel/CastVoteButtons.tsx @@ -3,11 +3,58 @@ import { useState } from 'react' import { ThumbUpIcon, ThumbDownIcon } from '@heroicons/react/solid' import Button from '../Button' import VoteCommentModal from '../VoteCommentModal' -import { useIsInCoolOffTime, useIsVoting, useVotingPop } from './hooks' +import { + useIsInCoolOffTime, + useIsVoting, + useVoterTokenRecord, + useVotingPop, +} from './hooks' +import { VotingClientType } from '@utils/uiTypes/VotePlugin' +import useVotePluginsClientStore from 'stores/useVotePluginsClientStore' +import useWalletOnePointOh from '@hooks/useWalletOnePointOh' import { useProposalVoteRecordQuery } from '@hooks/queries/voteRecord' import { useSubmitVote } from '@hooks/useSubmitVote' import { useSelectedRealmInfo } from '@hooks/selectedRealm/useSelectedRealmRegistryEntry' -import { useCanVote } from './useCanVote' +import { useGovernancePowerAsync } from '@hooks/queries/governancePower' + +export const useCanVote = () => { + const client = useVotePluginsClientStore( + (s) => s.state.currentRealmVotingClient + ) + const votingPop = useVotingPop() + const { result: govPower } = useGovernancePowerAsync(votingPop) + const wallet = useWalletOnePointOh() + const connected = !!wallet?.connected + + const { data: ownVoteRecord } = useProposalVoteRecordQuery('electoral') + const voterTokenRecord = useVoterTokenRecord() + + const isVoteCast = !!ownVoteRecord?.found + + const hasMinAmountToVote = voterTokenRecord && govPower?.gtn(0) + + const canVote = + connected && + !( + client.clientType === VotingClientType.NftVoterClient && !voterTokenRecord + ) && + !( + client.clientType === VotingClientType.HeliumVsrClient && + !voterTokenRecord + ) && + !isVoteCast && + hasMinAmountToVote + + const voteTooltipContent = !connected + ? 'You need to connect your wallet to be able to vote' + : client.clientType === VotingClientType.NftVoterClient && !voterTokenRecord + ? 'You must join the Realm to be able to vote' + : !hasMinAmountToVote + ? 'You don’t have governance power to vote in this dao' + : '' + + return [canVote, voteTooltipContent] as const +} export const CastVoteButtons = () => { const [showVoteModal, setShowVoteModal] = useState(false) @@ -16,6 +63,7 @@ export const CastVoteButtons = () => { const allowDiscussion = realmInfo?.allowDiscussion ?? true const { submitting, submitVote } = useSubmitVote() const votingPop = useVotingPop() + const voterTokenRecord = useVoterTokenRecord() const [canVote, tooltipContent] = useCanVote() const { data: ownVoteRecord } = useProposalVoteRecordQuery('electoral') @@ -31,6 +79,7 @@ export const CastVoteButtons = () => { } else { await submitVote({ vote: vote === 'yes' ? VoteKind.Approve : VoteKind.Deny, + voterTokenRecord: voterTokenRecord!, }) } } @@ -82,6 +131,7 @@ export const CastVoteButtons = () => { isOpen={showVoteModal} onClose={() => setShowVoteModal(false)} vote={vote === 'yes' ? VoteKind.Approve : VoteKind.Deny} + voterTokenRecord={voterTokenRecord!} /> ) : null}
diff --git a/components/VotePanel/VetoButtons.tsx b/components/VotePanel/VetoButtons.tsx index 334feabf40..597f6c6683 100644 --- a/components/VotePanel/VetoButtons.tsx +++ b/components/VotePanel/VetoButtons.tsx @@ -71,6 +71,7 @@ const VetoButtons = () => { const vetoingPop = useVetoingPop() const canVeto = useCanVeto() const [openModal, setOpenModal] = useState(false) + const voterTokenRecord = useUserVetoTokenRecord() const { data: userVetoRecord } = useProposalVoteRecordQuery('veto') const { submitting, submitVote } = useSubmitVote() @@ -80,11 +81,15 @@ const VetoButtons = () => { } else { submitVote({ vote: VoteKind.Veto, + voterTokenRecord: voterTokenRecord!, }) } } - return vetoable && vetoingPop && !userVetoRecord?.found ? ( + return vetoable && + vetoingPop && + voterTokenRecord && + !userVetoRecord?.found ? ( <>
@@ -111,6 +116,7 @@ const VetoButtons = () => { setOpenModal(false)} isOpen={openModal} + voterTokenRecord={voterTokenRecord} vote={VoteKind.Veto} /> ) : null} diff --git a/components/VotePanel/useCanVote.ts b/components/VotePanel/useCanVote.ts deleted file mode 100644 index 077491b323..0000000000 --- a/components/VotePanel/useCanVote.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { useVoterTokenRecord, useVotingPop } from './hooks' -import { VotingClientType } from '@utils/uiTypes/VotePlugin' -import useVotePluginsClientStore from 'stores/useVotePluginsClientStore' -import useWalletOnePointOh from '@hooks/useWalletOnePointOh' -import { useProposalVoteRecordQuery } from '@hooks/queries/voteRecord' -import { - determineVotingPowerType, - useGovernancePowerAsync, -} from '@hooks/queries/governancePower' - -import { useConnection } from '@solana/wallet-adapter-react' -import { useAsync } from 'react-async-hook' -import { useSelectedDelegatorStore } from 'stores/useSelectedDelegatorStore' - -import { DELEGATOR_BATCH_VOTE_SUPPORT_BY_PLUGIN } from '@constants/flags' -import useSelectedRealmPubkey from '@hooks/selectedRealm/useSelectedRealmPubkey' -import { useRealmQuery } from '@hooks/queries/realm' -import { useTokenOwnerRecordsDelegatedToUser } from '@hooks/queries/tokenOwnerRecord' - -const useHasAnyVotingPower = (role: 'community' | 'council' | undefined) => { - const realmPk = useSelectedRealmPubkey() - const realm = useRealmQuery().data?.result - - const { connection } = useConnection() - - const relevantMint = - role && role === 'community' - ? realm?.account.communityMint - : realm?.account.config.councilMint - - const { result: personalAmount } = useGovernancePowerAsync(role) - - const { result: plugin } = useAsync( - async () => - role && realmPk && determineVotingPowerType(connection, realmPk, role), - [connection, realmPk, role] - ) - - // DELEGATOR VOTING --------------------------------------------------------------- - - const batchVoteSupported = - plugin && DELEGATOR_BATCH_VOTE_SUPPORT_BY_PLUGIN[plugin] - // If the user is selecting a specific delegator, we want to just use that and not count the other delegators - const selectedDelegator = useSelectedDelegatorStore((s) => - role === 'community' ? s.communityDelegator : s.councilDelegator - ) - const torsDelegatedToUser = useTokenOwnerRecordsDelegatedToUser() - const relevantDelegators = selectedDelegator - ? undefined - : relevantMint && - torsDelegatedToUser?.filter((x) => - x.account.governingTokenMint.equals(relevantMint) - ) - - //--------------------------------------------------------------------------------- - // notably, this is ignoring whether the delegators actually have voting power, but it's not a big deal - const canBatchVote = - relevantDelegators === undefined || batchVoteSupported === undefined - ? undefined - : batchVoteSupported && relevantDelegators?.length !== 0 - - // technically, if you have a TOR you can vote even if there's no power. But that doesnt seem user friendly. - const canPersonallyVote = - personalAmount === undefined ? undefined : personalAmount.isZero() === false - - const canVote = canBatchVote || canPersonallyVote - - return canVote -} - -export const useCanVote = () => { - const client = useVotePluginsClientStore( - (s) => s.state.currentRealmVotingClient - ) - const votingPop = useVotingPop() - const wallet = useWalletOnePointOh() - const connected = !!wallet?.connected - - const { data: ownVoteRecord } = useProposalVoteRecordQuery('electoral') - const voterTokenRecord = useVoterTokenRecord() - - const isVoteCast = !!ownVoteRecord?.found - - const hasMinAmountToVote = useHasAnyVotingPower(votingPop) - - const canVote = - connected && - !( - client.clientType === VotingClientType.NftVoterClient && !voterTokenRecord - ) && - !( - client.clientType === VotingClientType.HeliumVsrClient && - !voterTokenRecord - ) && - !isVoteCast && - hasMinAmountToVote - - const voteTooltipContent = !connected - ? 'You need to connect your wallet to be able to vote' - : client.clientType === VotingClientType.NftVoterClient && !voterTokenRecord - ? 'You must join the Realm to be able to vote' - : !hasMinAmountToVote - ? 'You don’t have governance power to vote in this dao' - : '' - - return [canVote, voteTooltipContent] as const -} diff --git a/constants/flags.ts b/constants/flags.ts index a928eb4260..548a5d1b9c 100644 --- a/constants/flags.ts +++ b/constants/flags.ts @@ -1,17 +1,3 @@ -import { findPluginName } from './plugins' - export const SUPPORT_CNFTS = true export const ON_NFT_VOTER_V2 = false export const SHOW_DELEGATORS_LIST = false - -export const DELEGATOR_BATCH_VOTE_SUPPORT_BY_PLUGIN: Record< - ReturnType, - boolean -> = { - vanilla: true, - VSR: false, - HeliumVSR: false, - gateway: false, - NFT: false, - unknown: false, -} diff --git a/constants/plugins.ts b/constants/plugins.ts index c18345efdf..7f7808a8a4 100644 --- a/constants/plugins.ts +++ b/constants/plugins.ts @@ -1,5 +1,4 @@ -import { PROGRAM_ID as HELIUM_VSR_PROGRAM_ID } from '@helium/voter-stake-registry-sdk' -import { PublicKey } from '@solana/web3.js' +import * as heliumVsrSdk from '@helium/voter-stake-registry-sdk' import { DEFAULT_NFT_VOTER_PLUGIN } from '@tools/constants' export const VSR_PLUGIN_PKS: string[] = [ @@ -11,7 +10,7 @@ export const VSR_PLUGIN_PKS: string[] = [ ] export const HELIUM_VSR_PLUGINS_PKS: string[] = [ - HELIUM_VSR_PROGRAM_ID.toBase58(), + heliumVsrSdk.PROGRAM_ID.toBase58(), ] export const NFT_PLUGINS_PKS: string[] = [ @@ -24,16 +23,3 @@ export const GATEWAY_PLUGINS_PKS: string[] = [ 'Ggatr3wgDLySEwA2qEjt1oiw4BUzp5yMLJyz21919dq6', 'GgathUhdrCWRHowoRKACjgWhYHfxCEdBi5ViqYN6HVxk', // v2, supporting composition ] - -export const findPluginName = (programId: PublicKey | undefined) => - programId === undefined - ? ('vanilla' as const) - : VSR_PLUGIN_PKS.includes(programId.toString()) - ? ('VSR' as const) - : HELIUM_VSR_PLUGINS_PKS.includes(programId.toString()) - ? 'HeliumVSR' - : NFT_PLUGINS_PKS.includes(programId.toString()) - ? 'NFT' - : GATEWAY_PLUGINS_PKS.includes(programId.toString()) - ? 'gateway' - : 'unknown' diff --git a/hooks/queries/governancePower.ts b/hooks/queries/governancePower.ts index f6c6a340a3..51cd17dbb2 100644 --- a/hooks/queries/governancePower.ts +++ b/hooks/queries/governancePower.ts @@ -102,19 +102,6 @@ export const getNftGovpower = async ( return power } -export const findPluginName = (programId: PublicKey | undefined) => - programId === undefined - ? ('vanilla' as const) - : VSR_PLUGIN_PKS.includes(programId.toString()) - ? ('VSR' as const) - : HELIUM_VSR_PLUGINS_PKS.includes(programId.toString()) - ? 'HeliumVSR' - : NFT_PLUGINS_PKS.includes(programId.toString()) - ? 'NFT' - : GATEWAY_PLUGINS_PKS.includes(programId.toString()) - ? 'gateway' - : 'unknown' - export const determineVotingPowerType = async ( connection: Connection, realmPk: PublicKey, @@ -129,7 +116,17 @@ export const determineVotingPowerType = async ( ? config.result?.account.communityTokenConfig.voterWeightAddin : config.result?.account.councilTokenConfig.voterWeightAddin - return findPluginName(programId) + return programId === undefined + ? ('vanilla' as const) + : VSR_PLUGIN_PKS.includes(programId.toString()) + ? ('VSR' as const) + : HELIUM_VSR_PLUGINS_PKS.includes(programId.toString()) + ? 'HeliumVSR' + : NFT_PLUGINS_PKS.includes(programId.toString()) + ? 'NFT' + : GATEWAY_PLUGINS_PKS.includes(programId.toString()) + ? 'gateway' + : 'unknown' } export const useGovernancePowerAsync = ( diff --git a/hooks/queries/proposal.ts b/hooks/queries/proposal.ts index b91cba89f3..82e0b44717 100644 --- a/hooks/queries/proposal.ts +++ b/hooks/queries/proposal.ts @@ -1,4 +1,4 @@ -import { Connection, PublicKey } from '@solana/web3.js' +import { PublicKey } from '@solana/web3.js' import { useQuery } from '@tanstack/react-query' import asFindable from '@utils/queries/asFindable' import { @@ -27,15 +27,6 @@ export const proposalQueryKeys = { ], } -export const fetchProposalByPubkeyQuery = ( - connection: Connection, - pubkey: PublicKey -) => - queryClient.fetchQuery({ - queryKey: proposalQueryKeys.byPubkey(connection.rpcEndpoint, pubkey), - queryFn: () => asFindable(getProposal)(connection, pubkey), - }) - export const useProposalByPubkeyQuery = (pubkey: PublicKey | undefined) => { const connection = useLegacyConnectionContext() @@ -50,6 +41,7 @@ export const useProposalByPubkeyQuery = (pubkey: PublicKey | undefined) => { }, enabled, }) + return query } diff --git a/hooks/useSubmitVote.ts b/hooks/useSubmitVote.ts index 230819cd84..10a14f962e 100644 --- a/hooks/useSubmitVote.ts +++ b/hooks/useSubmitVote.ts @@ -9,10 +9,10 @@ import { ProgramAccount, Proposal, RpcContext, + TokenOwnerRecord, Vote, VoteChoice, VoteKind, - getTokenOwnerRecordAddress, withCastVote, } from '@solana/spl-governance' import { getProgramVersionForRealm } from '@models/registry/api' @@ -30,8 +30,6 @@ import { TransactionInstruction } from '@solana/web3.js' import useProgramVersion from './useProgramVersion' import useVotingTokenOwnerRecords from './useVotingTokenOwnerRecords' import { useMemo } from 'react' -import { useTokenOwnerRecordsDelegatedToUser } from './queries/tokenOwnerRecord' -import useUserOrDelegator from './useUserOrDelegator' export const useSubmitVote = () => { const wallet = useWalletOnePointOh() @@ -51,25 +49,20 @@ export const useSubmitVote = () => { config?.account.communityTokenConfig.voterWeightAddin?.toBase58() ) - const actingAsWalletPk = useUserOrDelegator() - const delegators = useTokenOwnerRecordsDelegatedToUser() - const { error, loading, execute } = useAsyncCallback( async ({ vote, + voterTokenRecord, comment, voteWeights, }: { vote: VoteKind + voterTokenRecord: ProgramAccount comment?: string voteWeights?: number[] }) => { - if (!proposal) throw new Error() - if (!realm) throw new Error() - if (!actingAsWalletPk) throw new Error() - const rpcContext = new RpcContext( - proposal.owner, + proposal!.owner, getProgramVersionForRealm(realmInfo!), wallet!, connection.current, @@ -89,41 +82,17 @@ export const useSubmitVote = () => { ) } - const relevantMint = - vote !== VoteKind.Veto - ? // if its not a veto, business as usual - proposal.account.governingTokenMint - : // if it is a veto, the vetoing mint is the opposite of the governing mint - realm.account.communityMint.equals( - proposal.account.governingTokenMint - ) - ? realm.account.config.councilMint - : realm.account.communityMint - if (relevantMint === undefined) throw new Error() - - const tokenOwnerRecordPk = await getTokenOwnerRecordAddress( - realm.owner, - realm.pubkey, - relevantMint, - actingAsWalletPk - ) - - const relevantDelegators = delegators - ?.filter((x) => x.account.governingTokenMint.equals(relevantMint)) - .map((x) => x.pubkey) - try { await castVote( rpcContext, - realm, - proposal, - tokenOwnerRecordPk, + realm!, + proposal!, + voterTokenRecord.pubkey, vote, msg, client, confirmationCallback, - voteWeights, - relevantDelegators + voteWeights ) queryClient.invalidateQueries({ queryKey: proposalQueryKeys.all(connection.current.rpcEndpoint),