diff --git a/packages/voter-stake-registry-hooks/src/hooks/useRelinquishVote.ts b/packages/voter-stake-registry-hooks/src/hooks/useRelinquishVote.ts index 31d846395..1815ed0cc 100644 --- a/packages/voter-stake-registry-hooks/src/hooks/useRelinquishVote.ts +++ b/packages/voter-stake-registry-hooks/src/hooks/useRelinquishVote.ts @@ -1,8 +1,4 @@ -import { - Status, - batchParallelInstructions, - truthy -} from "@helium/spl-utils"; +import { Status, batchParallelInstructions, truthy } from "@helium/spl-utils"; import { init, voteMarkerKey } from "@helium/voter-stake-registry-sdk"; import { PublicKey, TransactionInstruction } from "@solana/web3.js"; import { useCallback, useMemo } from "react"; @@ -10,30 +6,60 @@ import { useAsyncCallback } from "react-async-hook"; import { useHeliumVsrState } from "../contexts/heliumVsrContext"; import { useVoteMarkers } from "./useVoteMarkers"; import { MAX_TRANSACTIONS_PER_SIGNATURE_BATCH } from "../constants"; +import { useSolanaUnixNow } from "@helium/helium-react-hooks"; +import { calcPositionVotingPower } from "../utils/calcPositionVotingPower"; +import BN from "bn.js"; +import { proxyAssignmentKey } from "@helium/nft-proxy-sdk"; export const useRelinquishVote = (proposal: PublicKey) => { - const { positions, provider } = useHeliumVsrState(); + const { positions, provider, registrar } = useHeliumVsrState(); + const unixNow = useSolanaUnixNow(); + const sortedPositions = useMemo(() => { + return ( + unixNow && + positions?.sort((a, b) => { + return -calcPositionVotingPower({ + position: a, + registrar: registrar || null, + unixNow: new BN(unixNow), + }).cmp( + calcPositionVotingPower({ + position: b, + registrar: registrar || null, + unixNow: new BN(unixNow), + }) + ); + }) + ); + }, [positions, unixNow]); const voteMarkerKeys = useMemo(() => { - return positions - ? positions.map((p) => voteMarkerKey(p.mint, proposal)[0]) + return sortedPositions + ? sortedPositions.map((p) => voteMarkerKey(p.mint, proposal)[0]) : []; - }, [positions]); + }, [sortedPositions]); const { accounts: markers } = useVoteMarkers(voteMarkerKeys); + const canPositionRelinquishVote = useCallback( + (index: number, choice: number) => { + const position = sortedPositions?.[index]; + const marker = markers?.[index]?.info; + const earlierDelegateVoted = + position && + position.proxy && + marker && + position.proxy.index > marker.proxyIndex; + return !earlierDelegateVoted && marker?.choices.includes(choice); + }, + [markers] + ); const canRelinquishVote = useCallback( (choice: number) => { if (!markers) return false; - return markers.some((m, index) => { - const position = positions?.[index]; - const earlierDelegateVoted = - position && - position.proxy && - m.info && - position.proxy.index > m.info.proxyIndex; - return !earlierDelegateVoted && m.info?.choices.includes(choice); - }); + return markers.some((_, index) => + canPositionRelinquishVote(index, choice) + ); }, - [markers] + [markers, canPositionRelinquishVote] ); const { error, loading, execute } = useAsyncCallback( @@ -50,7 +76,8 @@ export const useRelinquishVote = (proposal: PublicKey) => { onProgress?: (status: Status) => void; maxSignatureBatch?: number; }) => { - const isInvalid = !provider || !positions || positions.length === 0; + const isInvalid = + !provider || !sortedPositions || sortedPositions.length === 0; if (isInvalid) { throw new Error( @@ -60,16 +87,16 @@ export const useRelinquishVote = (proposal: PublicKey) => { const vsrProgram = await init(provider); const instructions = ( await Promise.all( - positions.map(async (position, index) => { + sortedPositions.map(async (position, index) => { + const canRelinquishVote = canPositionRelinquishVote( + index, + choice + ); const marker = markers?.[index]?.info; - const alreadyVotedThisChoice = marker?.choices.includes(choice); - if (marker && alreadyVotedThisChoice) { + if (marker && canRelinquishVote) { if (position.isProxiedToMe) { - if ( - marker.proxyIndex < - (position.proxy?.index || 0) - ) { + if (marker.proxyIndex < (position.proxy?.index || 0)) { // Do not vote with a position that has been delegated to us, but voting overidden return; } @@ -82,6 +109,12 @@ export const useRelinquishVote = (proposal: PublicKey) => { proposal, voter: provider.wallet.publicKey, position: position.pubkey, + marker: voteMarkerKey(position.mint, proposal)[0], + proxyAssignment: proxyAssignmentKey( + registrar!.proxyConfig, + position.mint, + provider.wallet.publicKey, + )[0], }) .instruction(); } @@ -109,7 +142,7 @@ export const useRelinquishVote = (proposal: PublicKey) => { onProgress, triesRemaining: 10, extraSigners: [], - maxSignatureBatch + maxSignatureBatch, }); } } diff --git a/packages/voter-stake-registry-hooks/src/hooks/useVote.ts b/packages/voter-stake-registry-hooks/src/hooks/useVote.ts index 9182cc82c..57ef78eb0 100644 --- a/packages/voter-stake-registry-hooks/src/hooks/useVote.ts +++ b/packages/voter-stake-registry-hooks/src/hooks/useVote.ts @@ -14,25 +14,29 @@ import { useVoteMarkers } from "./useVoteMarkers"; import { calcPositionVotingPower } from "../utils/calcPositionVotingPower"; import { proxyAssignmentKey } from "@helium/nft-proxy-sdk"; import { useSolanaUnixNow } from "@helium/helium-react-hooks"; +import { PositionWithMeta } from "../sdk/types"; export const useVote = (proposalKey: PublicKey) => { const { info: proposal } = useProposal(proposalKey); const { positions, provider, registrar } = useHeliumVsrState(); - const unixNow = useSolanaUnixNow() + const unixNow = useSolanaUnixNow(); const sortedPositions = useMemo(() => { - return unixNow && positions?.sort((a, b) => { - return -calcPositionVotingPower({ - position: a, - registrar: registrar || null, - unixNow: new BN(unixNow), - }).cmp( - calcPositionVotingPower({ - position: b, + return ( + unixNow && + positions?.sort((a, b) => { + return -calcPositionVotingPower({ + position: a, registrar: registrar || null, unixNow: new BN(unixNow), - }) - ); - }); + }).cmp( + calcPositionVotingPower({ + position: b, + registrar: registrar || null, + unixNow: new BN(unixNow), + }) + ); + }) + ); }, [positions, unixNow]); const voteMarkerKeys = useMemo(() => { return sortedPositions @@ -82,33 +86,50 @@ export const useVote = (proposalKey: PublicKey) => { ); } }, [markers, sortedPositions]); - const canVote = useCallback( - (choice: number) => { - if (!markers) return false; + const canPositionVote = useCallback( + (index: number, choice: number) => { + const position = sortedPositions?.[index]; + const marker = markers?.[index]; - return markers.some((m, index) => { - const position = sortedPositions?.[index]; - const earlierDelegateVoted = - position && - position.proxy && - m.info && - position.proxy.index > m.info.proxyIndex; - const noMarker = !m?.info; - const maxChoicesReached = - (m?.info?.choices.length || 0) >= (proposal?.maxChoicesPerVoter || 0); - const alreadyVotedThisChoice = m.info?.choices.includes(choice); - const proxyExpired = - position?.proxy?.expirationTime && - new BN(position.proxy.expirationTime).lt(new BN(Date.now() / 1000)); - const canVote = !proxyExpired && - (noMarker || + const earlierDelegateVoted = + position && + position.proxy && + marker?.info && + position.proxy.index > marker.info.proxyIndex; + const noMarker = !marker?.info; + const maxChoicesReached = + (marker?.info?.choices.length || 0) >= + (proposal?.maxChoicesPerVoter || 0); + const alreadyVotedThisChoice = marker?.info?.choices.includes(choice); + const now = unixNow && new BN(unixNow); + const proxyExpired = + position?.proxy?.expirationTime && + now && + new BN(position.proxy.expirationTime).lt(now); + const votingPowerIsZero = + now && + calcPositionVotingPower({ + position, + registrar: registrar || null, + unixNow: now, + }).isZero(); + const canVote = + !proxyExpired && + !votingPowerIsZero && + (noMarker || (!maxChoicesReached && !alreadyVotedThisChoice && !earlierDelegateVoted)); - return canVote; - }); + return canVote; }, - [markers] + [registrar, unixNow] + ); + const canVote = useCallback( + (choice: number) => { + if (!markers) return false; + return markers.some((_, index) => canPositionVote(index, choice)); + }, + [markers, canPositionVote] ); const { error, loading, execute } = useAsyncCallback( async ({ @@ -132,86 +153,56 @@ export const useVote = (proposalKey: PublicKey) => { "Unable to vote without positions. Please stake tokens first." ); } else { - const clock = await provider.connection.getAccountInfo( - SYSVAR_CLOCK_PUBKEY - ); const vsrProgram = await init(provider); const instructions = ( await Promise.all( // vote with bigger positions first. - sortedPositions - .map(async (position, index) => { - const marker = markers?.[index]?.info; - const alreadyVotedThisChoice = marker?.choices.includes(choice); - const maxChoicesReached = - (marker?.choices.length || 0) >= - (proposal?.maxChoicesPerVoter || 0); - - const proxyExpired = - position?.proxy?.expirationTime && - unixNow && - new BN(position.proxy.expirationTime).lt(new BN(unixNow)); - - // Ignore positions that have 0 voting power and haven't already voted - if ( - proxyExpired || - (!marker && unixNow && - calcPositionVotingPower({ - position, - registrar: registrar || null, - unixNow: new BN(unixNow), - }).isZero()) - ) { - return; - } - if ( - !marker || - (!alreadyVotedThisChoice && !maxChoicesReached) - ) { - if (position.isProxiedToMe) { - if ( - marker && - (marker.proxyIndex < (position.proxy?.index || 0) || - marker.choices.includes(choice)) - ) { - // Do not vote with a position that has been delegated to us, but voting overidden - // Also ignore voting for the same choice twice - return; - } - - return await vsrProgram.methods - .proxiedVoteV0({ - choice, - }) - .accounts({ - proposal: proposalKey, - voter: provider.wallet.publicKey, - position: position.pubkey, - registrar: registrar?.pubkey, - marker: voteMarkerKey( - position.mint, - proposalKey - )[0], - proxyAssignment: proxyAssignmentKey( - registrar!.proxyConfig, - position.mint, - provider.wallet.publicKey - )[0], - }) - .instruction(); + sortedPositions.map(async (position, index) => { + const marker = markers?.[index]?.info; + const canVote = canPositionVote(index, choice); + if (canVote) { + if (position.isProxiedToMe) { + if ( + marker && + (marker.proxyIndex < (position.proxy?.index || 0) || + marker.choices.includes(choice)) + ) { + // Do not vote with a position that has been delegated to us, but voting overidden + // Also ignore voting for the same choice twice + return; } + return await vsrProgram.methods - .voteV0({ + .proxiedVoteV0({ choice, }) .accounts({ proposal: proposalKey, voter: provider.wallet.publicKey, position: position.pubkey, + registrar: registrar?.pubkey, + marker: voteMarkerKey(position.mint, proposalKey)[0], + proxyAssignment: proxyAssignmentKey( + registrar!.proxyConfig, + position.mint, + provider.wallet.publicKey + )[0], }) .instruction(); } - }) + return await vsrProgram.methods + .voteV0({ + choice, + }) + .accounts({ + proposal: proposalKey, + voter: provider.wallet.publicKey, + position: position.pubkey, + marker: voteMarkerKey(position.mint, proposalKey)[0], + }) + .instruction(); + } + }) ) ).filter(truthy);